2 * Implements the message store.
4 * Copyright (c) 1987-2012 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.
18 #include <libcitadel.h>
22 #include "ctdl_module.h"
23 #include "citserver.h"
25 #include "clientsocket.h"
30 #include "internet_addressing.h"
31 #include "euidindex.h"
33 #include "journaling.h"
35 struct addresses_to_be_filed *atbf = NULL;
37 /* This temp file holds the queue of operations for AdjRefCount() */
38 static FILE *arcfp = NULL;
39 void AdjRefCountList(long *msgnum, long nmsg, int incr);
41 int MessageDebugEnabled = 0;
44 * These are the four-character field headers we use when outputting
45 * messages in Citadel format (as opposed to RFC822 format).
48 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
49 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
50 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
51 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
52 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
53 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
54 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
55 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
85 eMsgField FieldOrder[] = {
86 /* Important fields */
97 /* Semi-important fields */
103 /* G is not used yet, may become virus signature*/
106 /* Q is not used yet */
109 /* X is not used yet */
110 /* Z is not used yet */
117 /* Message text (MUST be last) */
119 /* Not saved to disk:
124 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
126 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
128 return !((Msg->cm_fields[which] != NULL) &&
129 (Msg->cm_fields[which][0] != '\0'));
132 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
134 if (Msg->cm_fields[which] != NULL)
135 free (Msg->cm_fields[which]);
136 Msg->cm_fields[which] = malloc(length + 1);
137 memcpy(Msg->cm_fields[which], buf, length);
138 Msg->cm_fields[which][length] = '\0';
139 Msg->cm_lengths[which] = length;
142 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
146 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
147 CM_SetField(Msg, which, buf, len);
149 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
151 if (Msg->cm_fields[WhichToCut] == NULL)
154 if (Msg->cm_lengths[WhichToCut] > maxlen)
156 Msg->cm_fields[WhichToCut][maxlen] = '\0';
157 Msg->cm_lengths[WhichToCut] = maxlen;
161 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
163 if (Msg->cm_fields[which] != NULL)
164 free (Msg->cm_fields[which]);
165 Msg->cm_fields[which] = NULL;
166 Msg->cm_lengths[which] = 0;
168 void CM_Flush(struct CtdlMessage *Msg)
172 if (CM_IsValidMsg(Msg) == 0)
175 for (i = 0; i < 256; ++i)
177 CM_FlushField(Msg, i);
181 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
184 if (Msg->cm_fields[WhichToPutTo] != NULL)
185 free (Msg->cm_fields[WhichToPutTo]);
187 if (Msg->cm_fields[WhichtToCopy] != NULL)
189 len = Msg->cm_lengths[WhichtToCopy];
190 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
191 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
192 Msg->cm_fields[WhichToPutTo][len] = '\0';
193 Msg->cm_lengths[WhichToPutTo] = len;
197 Msg->cm_fields[WhichToPutTo] = NULL;
198 Msg->cm_lengths[WhichToPutTo] = 0;
203 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
205 if (Msg->cm_fields[which] != NULL) {
210 oldmsgsize = Msg->cm_lengths[which] + 1;
211 newmsgsize = length + oldmsgsize;
213 new = malloc(newmsgsize);
214 memcpy(new, buf, length);
215 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
216 free(Msg->cm_fields[which]);
217 Msg->cm_fields[which] = new;
218 Msg->cm_lengths[which] = newmsgsize - 1;
221 Msg->cm_fields[which] = malloc(length + 1);
222 memcpy(Msg->cm_fields[which], buf, length);
223 Msg->cm_fields[which][length] = '\0';
224 Msg->cm_lengths[which] = length;
228 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
230 if (Msg->cm_fields[which] != NULL)
231 free (Msg->cm_fields[which]);
233 Msg->cm_fields[which] = *buf;
235 Msg->cm_lengths[which] = length;
238 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
240 if (Msg->cm_fields[which] != NULL)
241 free (Msg->cm_fields[which]);
243 Msg->cm_lengths[which] = StrLength(*buf);
244 Msg->cm_fields[which] = SmashStrBuf(buf);
247 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
249 if (Msg->cm_fields[which] != NULL)
251 *retlen = Msg->cm_lengths[which];
252 *ret = Msg->cm_fields[which];
253 Msg->cm_fields[which] = NULL;
254 Msg->cm_lengths[which] = 0;
264 * Returns 1 if the supplied pointer points to a valid Citadel message.
265 * If the pointer is NULL or the magic number check fails, returns 0.
267 int CM_IsValidMsg(struct CtdlMessage *msg) {
270 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
271 struct CitContext *CCC = CC;
272 MSGM_syslog(LOG_WARNING, "CM_IsValidMsg() -- self-check failed\n");
278 void CM_FreeContents(struct CtdlMessage *msg)
282 for (i = 0; i < 256; ++i)
283 if (msg->cm_fields[i] != NULL) {
284 free(msg->cm_fields[i]);
285 msg->cm_lengths[i] = 0;
288 msg->cm_magic = 0; /* just in case */
291 * 'Destructor' for struct CtdlMessage
293 void CM_Free(struct CtdlMessage *msg)
295 if (CM_IsValidMsg(msg) == 0)
297 if (msg != NULL) free (msg);
300 CM_FreeContents(msg);
304 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
307 len = OrgMsg->cm_lengths[i];
308 NewMsg->cm_fields[i] = malloc(len + 1);
309 if (NewMsg->cm_fields[i] == NULL)
311 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
312 NewMsg->cm_fields[i][len] = '\0';
313 NewMsg->cm_lengths[i] = len;
317 struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
320 struct CtdlMessage *NewMsg;
322 if (CM_IsValidMsg(OrgMsg) == 0)
324 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
328 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
330 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
332 for (i = 0; i < 256; ++i)
334 if (OrgMsg->cm_fields[i] != NULL)
336 if (!CM_DupField(i, OrgMsg, NewMsg))
351 /* Determine if a given message matches the fields in a message template.
352 * Return 0 for a successful match.
354 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
357 /* If there aren't any fields in the template, all messages will
360 if (template == NULL) return(0);
362 /* Null messages are bogus. */
363 if (msg == NULL) return(1);
365 for (i='A'; i<='Z'; ++i) {
366 if (template->cm_fields[i] != NULL) {
367 if (msg->cm_fields[i] == NULL) {
368 /* Considered equal if temmplate is empty string */
369 if (IsEmptyStr(template->cm_fields[i])) continue;
372 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
373 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
378 /* All compares succeeded: we have a match! */
385 * Retrieve the "seen" message list for the current room.
387 void CtdlGetSeen(char *buf, int which_set) {
388 struct CitContext *CCC = CC;
391 /* Learn about the user and room in question */
392 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
394 if (which_set == ctdlsetseen_seen)
395 safestrncpy(buf, vbuf.v_seen, SIZ);
396 if (which_set == ctdlsetseen_answered)
397 safestrncpy(buf, vbuf.v_answered, SIZ);
403 * Manipulate the "seen msgs" string (or other message set strings)
405 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
406 int target_setting, int which_set,
407 struct ctdluser *which_user, struct ctdlroom *which_room) {
408 struct CitContext *CCC = CC;
409 struct cdbdata *cdbfr;
414 long hi = (-1L); /// TODO: we just write here. y?
423 char *is_set; /* actually an array of booleans */
425 /* Don't bother doing *anything* if we were passed a list of zero messages */
426 if (num_target_msgnums < 1) {
430 /* If no room was specified, we go with the current room. */
432 which_room = &CCC->room;
435 /* If no user was specified, we go with the current user. */
437 which_user = &CCC->user;
440 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
441 num_target_msgnums, target_msgnums[0],
442 (target_setting ? "SET" : "CLEAR"),
446 /* Learn about the user and room in question */
447 CtdlGetRelationship(&vbuf, which_user, which_room);
449 /* Load the message list */
450 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
452 msglist = (long *) cdbfr->ptr;
453 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
454 num_msgs = cdbfr->len / sizeof(long);
457 return; /* No messages at all? No further action. */
460 is_set = malloc(num_msgs * sizeof(char));
461 memset(is_set, 0, (num_msgs * sizeof(char)) );
463 /* Decide which message set we're manipulating */
465 case ctdlsetseen_seen:
466 vset = NewStrBufPlain(vbuf.v_seen, -1);
468 case ctdlsetseen_answered:
469 vset = NewStrBufPlain(vbuf.v_answered, -1);
476 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
477 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
478 for (i=0; i<num_msgs; ++i) {
479 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
481 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
482 for (k=0; k<num_target_msgnums; ++k) {
483 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
487 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
489 /* Translate the existing sequence set into an array of booleans */
490 setstr = NewStrBuf();
494 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
496 StrBufExtract_token(lostr, setstr, 0, ':');
497 if (StrBufNum_tokens(setstr, ':') >= 2) {
498 StrBufExtract_token(histr, setstr, 1, ':');
502 StrBufAppendBuf(histr, lostr, 0);
505 if (!strcmp(ChrPtr(histr), "*")) {
512 for (i = 0; i < num_msgs; ++i) {
513 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
523 /* Now translate the array of booleans back into a sequence set */
529 for (i=0; i<num_msgs; ++i) {
533 for (k=0; k<num_target_msgnums; ++k) {
534 if (msglist[i] == target_msgnums[k]) {
535 is_seen = target_setting;
539 if ((was_seen == 0) && (is_seen == 1)) {
542 else if ((was_seen == 1) && (is_seen == 0)) {
545 if (StrLength(vset) > 0) {
546 StrBufAppendBufPlain(vset, HKEY(","), 0);
549 StrBufAppendPrintf(vset, "%ld", hi);
552 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
556 if ((is_seen) && (i == num_msgs - 1)) {
557 if (StrLength(vset) > 0) {
558 StrBufAppendBufPlain(vset, HKEY(","), 0);
560 if ((i==0) || (was_seen == 0)) {
561 StrBufAppendPrintf(vset, "%ld", msglist[i]);
564 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
572 * We will have to stuff this string back into a 4096 byte buffer, so if it's
573 * larger than that now, truncate it by removing tokens from the beginning.
574 * The limit of 100 iterations is there to prevent an infinite loop in case
575 * something unexpected happens.
577 int number_of_truncations = 0;
578 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
579 StrBufRemove_token(vset, 0, ',');
580 ++number_of_truncations;
584 * If we're truncating the sequence set of messages marked with the 'seen' flag,
585 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
586 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
588 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
590 first_tok = NewStrBuf();
591 StrBufExtract_token(first_tok, vset, 0, ',');
592 StrBufRemove_token(vset, 0, ',');
594 if (StrBufNum_tokens(first_tok, ':') > 1) {
595 StrBufRemove_token(first_tok, 0, ':');
599 new_set = NewStrBuf();
600 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
601 StrBufAppendBuf(new_set, first_tok, 0);
602 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
603 StrBufAppendBuf(new_set, vset, 0);
606 FreeStrBuf(&first_tok);
610 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
612 /* Decide which message set we're manipulating */
614 case ctdlsetseen_seen:
615 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
617 case ctdlsetseen_answered:
618 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
624 CtdlSetRelationship(&vbuf, which_user, which_room);
630 * API function to perform an operation for each qualifying message in the
631 * current room. (Returns the number of messages processed.)
633 int CtdlForEachMessage(int mode, long ref, char *search_string,
635 struct CtdlMessage *compare,
636 ForEachMsgCallback CallBack,
639 struct CitContext *CCC = CC;
642 struct cdbdata *cdbfr;
643 long *msglist = NULL;
645 int num_processed = 0;
648 struct CtdlMessage *msg = NULL;
651 int printed_lastold = 0;
652 int num_search_msgs = 0;
653 long *search_msgs = NULL;
655 int need_to_free_re = 0;
658 if ((content_type) && (!IsEmptyStr(content_type))) {
659 regcomp(&re, content_type, 0);
663 /* Learn about the user and room in question */
664 if (server_shutting_down) {
665 if (need_to_free_re) regfree(&re);
668 CtdlGetUser(&CCC->user, CCC->curr_user);
670 if (server_shutting_down) {
671 if (need_to_free_re) regfree(&re);
674 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
676 if (server_shutting_down) {
677 if (need_to_free_re) regfree(&re);
681 /* Load the message list */
682 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
684 if (need_to_free_re) regfree(&re);
685 return 0; /* No messages at all? No further action. */
688 msglist = (long *) cdbfr->ptr;
689 num_msgs = cdbfr->len / sizeof(long);
691 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
692 cdb_free(cdbfr); /* we own this memory now */
695 * Now begin the traversal.
697 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
699 /* If the caller is looking for a specific MIME type, filter
700 * out all messages which are not of the type requested.
702 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
704 /* This call to GetMetaData() sits inside this loop
705 * so that we only do the extra database read per msg
706 * if we need to. Doing the extra read all the time
707 * really kills the server. If we ever need to use
708 * metadata for another search criterion, we need to
709 * move the read somewhere else -- but still be smart
710 * enough to only do the read if the caller has
711 * specified something that will need it.
713 if (server_shutting_down) {
714 if (need_to_free_re) regfree(&re);
718 GetMetaData(&smi, msglist[a]);
720 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
721 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
727 num_msgs = sort_msglist(msglist, num_msgs);
729 /* If a template was supplied, filter out the messages which
730 * don't match. (This could induce some delays!)
733 if (compare != NULL) {
734 for (a = 0; a < num_msgs; ++a) {
735 if (server_shutting_down) {
736 if (need_to_free_re) regfree(&re);
740 msg = CtdlFetchMessage(msglist[a], 1);
742 if (CtdlMsgCmp(msg, compare)) {
751 /* If a search string was specified, get a message list from
752 * the full text index and remove messages which aren't on both
756 * Since the lists are sorted and strictly ascending, and the
757 * output list is guaranteed to be shorter than or equal to the
758 * input list, we overwrite the bottom of the input list. This
759 * eliminates the need to memmove big chunks of the list over and
762 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
764 /* Call search module via hook mechanism.
765 * NULL means use any search function available.
766 * otherwise replace with a char * to name of search routine
768 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
770 if (num_search_msgs > 0) {
774 orig_num_msgs = num_msgs;
776 for (i=0; i<orig_num_msgs; ++i) {
777 for (j=0; j<num_search_msgs; ++j) {
778 if (msglist[i] == search_msgs[j]) {
779 msglist[num_msgs++] = msglist[i];
785 num_msgs = 0; /* No messages qualify */
787 if (search_msgs != NULL) free(search_msgs);
789 /* Now that we've purged messages which don't contain the search
790 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
797 * Now iterate through the message list, according to the
798 * criteria supplied by the caller.
801 for (a = 0; a < num_msgs; ++a) {
802 if (server_shutting_down) {
803 if (need_to_free_re) regfree(&re);
805 return num_processed;
807 thismsg = msglist[a];
808 if (mode == MSGS_ALL) {
812 is_seen = is_msg_in_sequence_set(
813 vbuf.v_seen, thismsg);
814 if (is_seen) lastold = thismsg;
820 || ((mode == MSGS_OLD) && (is_seen))
821 || ((mode == MSGS_NEW) && (!is_seen))
822 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
823 || ((mode == MSGS_FIRST) && (a < ref))
824 || ((mode == MSGS_GT) && (thismsg > ref))
825 || ((mode == MSGS_LT) && (thismsg < ref))
826 || ((mode == MSGS_EQ) && (thismsg == ref))
829 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
831 CallBack(lastold, userdata);
835 if (CallBack) CallBack(thismsg, userdata);
839 if (need_to_free_re) regfree(&re);
842 * We cache the most recent msglist in order to do security checks later
844 if (CCC->client_socket > 0) {
845 if (CCC->cached_msglist != NULL) {
846 free(CCC->cached_msglist);
848 CCC->cached_msglist = msglist;
849 CCC->cached_num_msgs = num_msgs;
855 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 */
871 struct CitContext *CCC = CC;
873 unsigned char ch = 0;
880 while (ch=*(mptr++), ch != 0) {
883 if (client_write(outbuf, len) == -1)
885 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
889 if (client_write(nl, nllen) == -1)
891 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
896 else if (ch == '\r') {
897 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
899 else if (isspace(ch)) {
900 if (column > 72) { /* Beyond 72 columns, break on the next space */
901 if (client_write(outbuf, len) == -1)
903 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
907 if (client_write(nl, nllen) == -1)
909 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
922 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
923 if (client_write(outbuf, len) == -1)
925 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
929 if (client_write(nl, nllen) == -1)
931 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
939 if (client_write(outbuf, len) == -1)
941 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
944 client_write(nl, nllen);
952 * Callback function for mime parser that simply lists the part
954 void list_this_part(char *name, char *filename, char *partnum, char *disp,
955 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
956 char *cbid, void *cbuserdata)
960 ma = (struct ma_info *)cbuserdata;
961 if (ma->is_ma == 0) {
962 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
975 * Callback function for multipart prefix
977 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
978 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
979 char *cbid, void *cbuserdata)
983 ma = (struct ma_info *)cbuserdata;
984 if (!strcasecmp(cbtype, "multipart/alternative")) {
988 if (ma->is_ma == 0) {
989 cprintf("pref=%s|%s\n", partnum, cbtype);
994 * Callback function for multipart sufffix
996 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
997 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
998 char *cbid, void *cbuserdata)
1002 ma = (struct ma_info *)cbuserdata;
1003 if (ma->is_ma == 0) {
1004 cprintf("suff=%s|%s\n", partnum, cbtype);
1006 if (!strcasecmp(cbtype, "multipart/alternative")) {
1013 * Callback function for mime parser that opens a section for downloading
1014 * we use serv_files function here:
1016 extern void OpenCmdResult(char *filename, const char *mime_type);
1017 void mime_download(char *name, char *filename, char *partnum, char *disp,
1018 void *content, char *cbtype, char *cbcharset, size_t length,
1019 char *encoding, char *cbid, void *cbuserdata)
1022 CitContext *CCC = MyContext();
1024 /* Silently go away if there's already a download open. */
1025 if (CCC->download_fp != NULL)
1029 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1030 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1032 CCC->download_fp = tmpfile();
1033 if (CCC->download_fp == NULL) {
1034 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1036 cprintf("%d cannot open temporary file: %s\n",
1037 ERROR + INTERNAL_ERROR, strerror(errno));
1041 rv = fwrite(content, length, 1, CCC->download_fp);
1043 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1045 cprintf("%d unable to write tempfile.\n",
1047 fclose(CCC->download_fp);
1048 CCC->download_fp = NULL;
1051 fflush(CCC->download_fp);
1052 rewind(CCC->download_fp);
1054 OpenCmdResult(filename, cbtype);
1061 * Callback function for mime parser that outputs a section all at once.
1062 * We can specify the desired section by part number *or* content-id.
1064 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1065 void *content, char *cbtype, char *cbcharset, size_t length,
1066 char *encoding, char *cbid, void *cbuserdata)
1068 int *found_it = (int *)cbuserdata;
1071 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1072 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1075 cprintf("%d %d|-1|%s|%s|%s\n",
1082 client_write(content, length);
1088 * Load a message from disk into memory.
1089 * This is used by CtdlOutputMsg() and other fetch functions.
1091 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1092 * using the CtdlMessageFree() function.
1094 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1096 struct CitContext *CCC = CC;
1097 struct cdbdata *dmsgtext;
1098 struct CtdlMessage *ret = NULL;
1102 cit_uint8_t field_header;
1105 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1106 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1107 if (dmsgtext == NULL) {
1108 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1111 mptr = dmsgtext->ptr;
1112 upper_bound = mptr + dmsgtext->len;
1114 /* Parse the three bytes that begin EVERY message on disk.
1115 * The first is always 0xFF, the on-disk magic number.
1116 * The second is the anonymous/public type byte.
1117 * The third is the format type byte (vari, fixed, or MIME).
1121 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1125 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1126 memset(ret, 0, sizeof(struct CtdlMessage));
1128 ret->cm_magic = CTDLMESSAGE_MAGIC;
1129 ret->cm_anon_type = *mptr++; /* Anon type byte */
1130 ret->cm_format_type = *mptr++; /* Format type byte */
1133 * The rest is zero or more arbitrary fields. Load them in.
1134 * We're done when we encounter either a zero-length field or
1135 * have just processed the 'M' (message text) field.
1139 if (mptr >= upper_bound) {
1142 field_header = *mptr++;
1143 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'));
1153 /* Always make sure there's something in the msg text field. If
1154 * it's NULL, the message text is most likely stored separately,
1155 * so go ahead and fetch that. Failing that, just set a dummy
1156 * body so other code doesn't barf.
1158 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1159 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1160 if (dmsgtext != NULL) {
1161 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1165 if (CM_IsEmpty(ret, eMesageText)) {
1166 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1169 /* Perform "before read" hooks (aborting if any return nonzero) */
1170 if (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0) {
1181 * Pre callback function for multipart/alternative
1183 * NOTE: this differs from the standard behavior for a reason. Normally when
1184 * displaying multipart/alternative you want to show the _last_ usable
1185 * format in the message. Here we show the _first_ one, because it's
1186 * usually text/plain. Since this set of functions is designed for text
1187 * output to non-MIME-aware clients, this is the desired behavior.
1190 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1191 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1192 char *cbid, void *cbuserdata)
1194 struct CitContext *CCC = CC;
1197 ma = (struct ma_info *)cbuserdata;
1198 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1199 if (!strcasecmp(cbtype, "multipart/alternative")) {
1203 if (!strcasecmp(cbtype, "message/rfc822")) {
1209 * Post callback function for multipart/alternative
1211 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1212 void *content, char *cbtype, char *cbcharset, size_t length,
1213 char *encoding, char *cbid, void *cbuserdata)
1215 struct CitContext *CCC = CC;
1218 ma = (struct ma_info *)cbuserdata;
1219 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1220 if (!strcasecmp(cbtype, "multipart/alternative")) {
1224 if (!strcasecmp(cbtype, "message/rfc822")) {
1230 * Inline callback function for mime parser that wants to display text
1232 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1233 void *content, char *cbtype, char *cbcharset, size_t length,
1234 char *encoding, char *cbid, void *cbuserdata)
1236 struct CitContext *CCC = CC;
1242 ma = (struct ma_info *)cbuserdata;
1244 MSG_syslog(LOG_DEBUG,
1245 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1246 partnum, filename, cbtype, (long)length);
1249 * If we're in the middle of a multipart/alternative scope and
1250 * we've already printed another section, skip this one.
1252 if ( (ma->is_ma) && (ma->did_print) ) {
1253 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1258 if ( (!strcasecmp(cbtype, "text/plain"))
1259 || (IsEmptyStr(cbtype)) ) {
1262 client_write(wptr, length);
1263 if (wptr[length-1] != '\n') {
1270 if (!strcasecmp(cbtype, "text/html")) {
1271 ptr = html_to_ascii(content, length, 80, 0);
1273 client_write(ptr, wlen);
1274 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1281 if (ma->use_fo_hooks) {
1282 if (PerformFixedOutputHooks(cbtype, content, length)) {
1283 /* above function 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)
1310 struct CitContext *CCC = CC;
1315 ma = (struct ma_info *)cbuserdata;
1317 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1318 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1319 // I don't know if there are any side effects! Please TEST TEST TEST
1320 //if (ma->is_ma > 0) {
1322 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1323 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1324 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1325 if (i < ma->chosen_pref) {
1326 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1327 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1328 ma->chosen_pref = i;
1335 * Now that we've chosen our preferred part, output it.
1337 void output_preferred(char *name,
1349 struct CitContext *CCC = CC;
1352 int add_newline = 0;
1355 char *decoded = NULL;
1356 size_t bytes_decoded;
1359 ma = (struct ma_info *)cbuserdata;
1361 /* This is not the MIME part you're looking for... */
1362 if (strcasecmp(partnum, ma->chosen_part)) return;
1364 /* If the content-type of this part is in our preferred formats
1365 * list, we can simply output it verbatim.
1367 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1368 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1369 if (!strcasecmp(buf, cbtype)) {
1370 /* Yeah! Go! W00t!! */
1371 if (ma->dont_decode == 0)
1372 rc = mime_decode_now (content,
1378 break; /* Give us the chance, maybe theres another one. */
1380 if (rc == 0) text_content = (char *)content;
1382 text_content = decoded;
1383 length = bytes_decoded;
1386 if (text_content[length-1] != '\n') {
1389 cprintf("Content-type: %s", cbtype);
1390 if (!IsEmptyStr(cbcharset)) {
1391 cprintf("; charset=%s", cbcharset);
1393 cprintf("\nContent-length: %d\n",
1394 (int)(length + add_newline) );
1395 if (!IsEmptyStr(encoding)) {
1396 cprintf("Content-transfer-encoding: %s\n", encoding);
1399 cprintf("Content-transfer-encoding: 7bit\n");
1401 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1403 if (client_write(text_content, length) == -1)
1405 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1408 if (add_newline) cprintf("\n");
1409 if (decoded != NULL) free(decoded);
1414 /* No translations required or possible: output as text/plain */
1415 cprintf("Content-type: text/plain\n\n");
1417 if (ma->dont_decode == 0)
1418 rc = mime_decode_now (content,
1424 return; /* Give us the chance, maybe theres another one. */
1426 if (rc == 0) text_content = (char *)content;
1428 text_content = decoded;
1429 length = bytes_decoded;
1432 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1433 length, encoding, cbid, cbuserdata);
1434 if (decoded != NULL) free(decoded);
1439 char desired_section[64];
1446 * Callback function for
1448 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1449 void *content, char *cbtype, char *cbcharset, size_t length,
1450 char *encoding, char *cbid, void *cbuserdata)
1452 struct encapmsg *encap;
1454 encap = (struct encapmsg *)cbuserdata;
1456 /* Only proceed if this is the desired section... */
1457 if (!strcasecmp(encap->desired_section, partnum)) {
1458 encap->msglen = length;
1459 encap->msg = malloc(length + 2);
1460 memcpy(encap->msg, content, length);
1467 * Determine whether the specified message exists in the cached_msglist
1468 * (This is a security check)
1470 int check_cached_msglist(long msgnum) {
1471 struct CitContext *CCC = CC;
1473 /* cases in which we skip the check */
1474 if (!CCC) return om_ok; /* not a session */
1475 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1476 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1477 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1480 /* Do a binary search within the cached_msglist for the requested msgnum */
1482 int max = (CC->cached_num_msgs - 1);
1484 while (max >= min) {
1485 int middle = min + (max-min) / 2 ;
1486 if (msgnum == CCC->cached_msglist[middle]) {
1489 if (msgnum > CC->cached_msglist[middle]) {
1497 return om_access_denied;
1503 * Get a message off disk. (returns om_* values found in msgbase.h)
1506 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1507 int mode, /* how would you like that message? */
1508 int headers_only, /* eschew the message body? */
1509 int do_proto, /* do Citadel protocol responses? */
1510 int crlf, /* Use CRLF newlines instead of LF? */
1511 char *section, /* NULL or a message/rfc822 section */
1512 int flags, /* various flags; see msgbase.h */
1516 struct CitContext *CCC = CC;
1517 struct CtdlMessage *TheMessage = NULL;
1518 int retcode = CIT_OK;
1519 struct encapmsg encap;
1522 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1524 (section ? section : "<>")
1527 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1530 if (r == om_not_logged_in) {
1531 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1534 cprintf("%d An unknown error has occurred.\n", ERROR);
1541 * Check to make sure the message is actually IN this room
1543 r = check_cached_msglist(msg_num);
1544 if (r == om_access_denied) {
1545 /* Not in the cache? We get ONE shot to check it again. */
1546 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1547 r = check_cached_msglist(msg_num);
1550 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1551 msg_num, CCC->room.QRname
1554 if (r == om_access_denied) {
1555 cprintf("%d message %ld was not found in this room\n",
1556 ERROR + HIGHER_ACCESS_REQUIRED,
1565 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1566 * request that we don't even bother loading the body into memory.
1568 if (headers_only == HEADERS_FAST) {
1569 TheMessage = CtdlFetchMessage(msg_num, 0);
1572 TheMessage = CtdlFetchMessage(msg_num, 1);
1575 if (TheMessage == NULL) {
1576 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1577 ERROR + MESSAGE_NOT_FOUND, msg_num);
1578 return(om_no_such_msg);
1581 /* Here is the weird form of this command, to process only an
1582 * encapsulated message/rfc822 section.
1584 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1585 memset(&encap, 0, sizeof encap);
1586 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1587 mime_parser(TheMessage->cm_fields[eMesageText],
1589 *extract_encapsulated_message,
1590 NULL, NULL, (void *)&encap, 0
1593 if ((Author != NULL) && (*Author == NULL))
1595 *Author = TheMessage->cm_fields[eAuthor];
1596 TheMessage->cm_fields[eAuthor] = NULL;
1598 if ((Address != NULL) && (*Address == NULL))
1600 *Address = TheMessage->cm_fields[erFc822Addr];
1601 TheMessage->cm_fields[erFc822Addr] = NULL;
1603 CM_Free(TheMessage);
1607 encap.msg[encap.msglen] = 0;
1608 TheMessage = convert_internet_message(encap.msg);
1609 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1611 /* Now we let it fall through to the bottom of this
1612 * function, because TheMessage now contains the
1613 * encapsulated message instead of the top-level
1614 * message. Isn't that neat?
1619 cprintf("%d msg %ld has no part %s\n",
1620 ERROR + MESSAGE_NOT_FOUND,
1624 retcode = om_no_such_msg;
1629 /* Ok, output the message now */
1630 if (retcode == CIT_OK)
1631 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1632 if ((Author != NULL) && (*Author == NULL))
1634 *Author = TheMessage->cm_fields[eAuthor];
1635 TheMessage->cm_fields[eAuthor] = NULL;
1637 if ((Address != NULL) && (*Address == NULL))
1639 *Address = TheMessage->cm_fields[erFc822Addr];
1640 TheMessage->cm_fields[erFc822Addr] = NULL;
1643 CM_Free(TheMessage);
1650 void OutputCtdlMsgHeaders(
1651 struct CtdlMessage *TheMessage,
1652 int do_proto) /* do Citadel protocol responses? */
1657 char display_name[256];
1659 /* begin header processing loop for Citadel message format */
1660 safestrncpy(display_name, "<unknown>", sizeof display_name);
1661 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1662 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1663 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1664 safestrncpy(display_name, "****", sizeof display_name);
1666 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1667 safestrncpy(display_name, "anonymous", sizeof display_name);
1670 safestrncpy(display_name, buf, sizeof display_name);
1672 if ((is_room_aide())
1673 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1674 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1675 size_t tmp = strlen(display_name);
1676 snprintf(&display_name[tmp],
1677 sizeof display_name - tmp,
1682 /* Don't show Internet address for users on the
1683 * local Citadel network.
1686 if (!CM_IsEmpty(TheMessage, eNodeName) &&
1687 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
1692 /* Now spew the header fields in the order we like them. */
1693 for (i=0; i< NDiskFields; ++i) {
1695 Field = FieldOrder[i];
1696 if (Field != eMesageText) {
1697 if ( (!CM_IsEmpty(TheMessage, Field))
1698 && (msgkeys[Field] != NULL) ) {
1699 if ((Field == eenVelopeTo) ||
1700 (Field == eRecipient) ||
1701 (Field == eCarbonCopY)) {
1702 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1704 if (Field == eAuthor) {
1705 if (do_proto) cprintf("%s=%s\n",
1709 else if ((Field == erFc822Addr) && (suppress_f)) {
1712 /* Masquerade display name if needed */
1714 if (do_proto) cprintf("%s=%s\n",
1716 TheMessage->cm_fields[Field]
1725 void OutputRFC822MsgHeaders(
1726 struct CtdlMessage *TheMessage,
1727 int flags, /* should the bessage be exported clean */
1729 char *mid, long sizeof_mid,
1730 char *suser, long sizeof_suser,
1731 char *luser, long sizeof_luser,
1732 char *fuser, long sizeof_fuser,
1733 char *snode, long sizeof_snode)
1735 char datestamp[100];
1736 int subject_found = 0;
1743 for (i = 0; i < 256; ++i) {
1744 if (TheMessage->cm_fields[i]) {
1745 mptr = mpptr = TheMessage->cm_fields[i];
1748 safestrncpy(luser, mptr, sizeof_luser);
1749 safestrncpy(suser, mptr, sizeof_suser);
1751 else if (i == 'Y') {
1752 if ((flags & QP_EADDR) != 0) {
1753 mptr = qp_encode_email_addrs(mptr);
1755 sanitize_truncated_recipient(mptr);
1756 cprintf("CC: %s%s", mptr, nl);
1758 else if (i == 'P') {
1759 cprintf("Return-Path: %s%s", mptr, nl);
1761 else if (i == eListID) {
1762 cprintf("List-ID: %s%s", mptr, nl);
1764 else if (i == 'V') {
1765 if ((flags & QP_EADDR) != 0)
1766 mptr = qp_encode_email_addrs(mptr);
1768 while ((*hptr != '\0') && isspace(*hptr))
1770 if (!IsEmptyStr(hptr))
1771 cprintf("Envelope-To: %s%s", hptr, nl);
1773 else if (i == 'U') {
1774 cprintf("Subject: %s%s", mptr, nl);
1778 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1779 else if (i == erFc822Addr)
1780 safestrncpy(fuser, mptr, sizeof_fuser);
1781 /* else if (i == 'O')
1782 cprintf("X-Citadel-Room: %s%s",
1785 safestrncpy(snode, mptr, sizeof_snode);
1788 if (haschar(mptr, '@') == 0)
1790 sanitize_truncated_recipient(mptr);
1791 cprintf("To: %s@%s", mptr, config.c_fqdn);
1796 if ((flags & QP_EADDR) != 0) {
1797 mptr = qp_encode_email_addrs(mptr);
1799 sanitize_truncated_recipient(mptr);
1800 cprintf("To: %s", mptr);
1804 else if (i == 'T') {
1805 datestring(datestamp, sizeof datestamp,
1806 atol(mptr), DATESTRING_RFC822);
1807 cprintf("Date: %s%s", datestamp, nl);
1809 else if (i == 'W') {
1810 cprintf("References: ");
1811 k = num_tokens(mptr, '|');
1812 for (j=0; j<k; ++j) {
1813 extract_token(buf, mptr, j, '|', sizeof buf);
1814 cprintf("<%s>", buf);
1823 else if (i == eReplyTo) {
1825 while ((*hptr != '\0') && isspace(*hptr))
1827 if (!IsEmptyStr(hptr))
1828 cprintf("Reply-To: %s%s", mptr, nl);
1834 if (subject_found == 0) {
1835 cprintf("Subject: (no subject)%s", nl);
1840 void Dump_RFC822HeadersBody(
1841 struct CtdlMessage *TheMessage,
1842 int headers_only, /* eschew the message body? */
1843 int flags, /* should the bessage be exported clean? */
1847 cit_uint8_t prev_ch;
1849 const char *StartOfText = StrBufNOTNULL;
1852 int nllen = strlen(nl);
1855 mptr = TheMessage->cm_fields[eMesageText];
1859 while (*mptr != '\0') {
1860 if (*mptr == '\r') {
1867 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1869 eoh = *(mptr+1) == '\n';
1873 StartOfText = strchr(StartOfText, '\n');
1874 StartOfText = strchr(StartOfText, '\n');
1877 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1878 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1879 ((headers_only != HEADERS_NONE) &&
1880 (headers_only != HEADERS_ONLY))
1882 if (*mptr == '\n') {
1883 memcpy(&outbuf[outlen], nl, nllen);
1885 outbuf[outlen] = '\0';
1888 outbuf[outlen++] = *mptr;
1892 if (flags & ESC_DOT)
1894 if ((prev_ch == '\n') &&
1896 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1898 outbuf[outlen++] = '.';
1903 if (outlen > 1000) {
1904 if (client_write(outbuf, outlen) == -1)
1906 struct CitContext *CCC = CC;
1907 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
1914 client_write(outbuf, outlen);
1920 /* If the format type on disk is 1 (fixed-format), then we want
1921 * everything to be output completely literally ... regardless of
1922 * what message transfer format is in use.
1924 void DumpFormatFixed(
1925 struct CtdlMessage *TheMessage,
1926 int mode, /* how would you like that message? */
1933 int nllen = strlen (nl);
1936 mptr = TheMessage->cm_fields[eMesageText];
1938 if (mode == MT_MIME) {
1939 cprintf("Content-type: text/plain\n\n");
1943 while (ch = *mptr++, ch > 0) {
1947 if ((buflen > 250) && (!xlline)){
1951 while ((buflen > 0) &&
1952 (!isspace(buf[buflen])))
1958 mptr -= tbuflen - buflen;
1963 /* if we reach the outer bounds of our buffer,
1964 abort without respect what whe purge. */
1967 (buflen > SIZ - nllen - 2)))
1971 memcpy (&buf[buflen], nl, nllen);
1975 if (client_write(buf, buflen) == -1)
1977 struct CitContext *CCC = CC;
1978 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
1990 if (!IsEmptyStr(buf))
1991 cprintf("%s%s", buf, nl);
1995 * Get a message off disk. (returns om_* values found in msgbase.h)
1997 int CtdlOutputPreLoadedMsg(
1998 struct CtdlMessage *TheMessage,
1999 int mode, /* how would you like that message? */
2000 int headers_only, /* eschew the message body? */
2001 int do_proto, /* do Citadel protocol responses? */
2002 int crlf, /* Use CRLF newlines instead of LF? */
2003 int flags /* should the bessage be exported clean? */
2005 struct CitContext *CCC = CC;
2008 const char *nl; /* newline string */
2011 /* Buffers needed for RFC822 translation. These are all filled
2012 * using functions that are bounds-checked, and therefore we can
2013 * make them substantially smaller than SIZ.
2021 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2022 ((TheMessage == NULL) ? "NULL" : "not null"),
2023 mode, headers_only, do_proto, crlf);
2025 strcpy(mid, "unknown");
2026 nl = (crlf ? "\r\n" : "\n");
2028 if (!CM_IsValidMsg(TheMessage)) {
2029 MSGM_syslog(LOG_ERR,
2030 "ERROR: invalid preloaded message for output\n");
2032 return(om_no_such_msg);
2035 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2036 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2038 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2039 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2042 /* Are we downloading a MIME component? */
2043 if (mode == MT_DOWNLOAD) {
2044 if (TheMessage->cm_format_type != FMT_RFC822) {
2046 cprintf("%d This is not a MIME message.\n",
2047 ERROR + ILLEGAL_VALUE);
2048 } else if (CCC->download_fp != NULL) {
2049 if (do_proto) cprintf(
2050 "%d You already have a download open.\n",
2051 ERROR + RESOURCE_BUSY);
2053 /* Parse the message text component */
2054 mptr = TheMessage->cm_fields[eMesageText];
2055 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2056 /* If there's no file open by this time, the requested
2057 * section wasn't found, so print an error
2059 if (CCC->download_fp == NULL) {
2060 if (do_proto) cprintf(
2061 "%d Section %s not found.\n",
2062 ERROR + FILE_NOT_FOUND,
2063 CCC->download_desired_section);
2066 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2069 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2070 * in a single server operation instead of opening a download file.
2072 if (mode == MT_SPEW_SECTION) {
2073 if (TheMessage->cm_format_type != FMT_RFC822) {
2075 cprintf("%d This is not a MIME message.\n",
2076 ERROR + ILLEGAL_VALUE);
2078 /* Parse the message text component */
2081 mptr = TheMessage->cm_fields[eMesageText];
2082 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2083 /* If section wasn't found, print an error
2086 if (do_proto) cprintf(
2087 "%d Section %s not found.\n",
2088 ERROR + FILE_NOT_FOUND,
2089 CCC->download_desired_section);
2092 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2095 /* now for the user-mode message reading loops */
2096 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2098 /* Does the caller want to skip the headers? */
2099 if (headers_only == HEADERS_NONE) goto START_TEXT;
2101 /* Tell the client which format type we're using. */
2102 if ( (mode == MT_CITADEL) && (do_proto) ) {
2103 cprintf("type=%d\n", TheMessage->cm_format_type);
2106 /* nhdr=yes means that we're only displaying headers, no body */
2107 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2108 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2111 cprintf("nhdr=yes\n");
2114 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2115 OutputCtdlMsgHeaders(TheMessage, do_proto);
2118 /* begin header processing loop for RFC822 transfer format */
2122 strcpy(snode, NODENAME);
2123 if (mode == MT_RFC822)
2124 OutputRFC822MsgHeaders(
2129 suser, sizeof(suser),
2130 luser, sizeof(luser),
2131 fuser, sizeof(fuser),
2132 snode, sizeof(snode)
2136 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2137 suser[i] = tolower(suser[i]);
2138 if (!isalnum(suser[i])) suser[i]='_';
2141 if (mode == MT_RFC822) {
2142 if (!strcasecmp(snode, NODENAME)) {
2143 safestrncpy(snode, FQDN, sizeof snode);
2146 /* Construct a fun message id */
2147 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2148 if (strchr(mid, '@')==NULL) {
2149 cprintf("@%s", snode);
2153 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2154 cprintf("From: \"----\" <x@x.org>%s", nl);
2156 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2157 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2159 else if (!IsEmptyStr(fuser)) {
2160 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2163 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2166 /* Blank line signifying RFC822 end-of-headers */
2167 if (TheMessage->cm_format_type != FMT_RFC822) {
2172 /* end header processing loop ... at this point, we're in the text */
2174 if (headers_only == HEADERS_FAST) goto DONE;
2176 /* Tell the client about the MIME parts in this message */
2177 if (TheMessage->cm_format_type == FMT_RFC822) {
2178 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2179 mptr = TheMessage->cm_fields[eMesageText];
2180 memset(&ma, 0, sizeof(struct ma_info));
2181 mime_parser(mptr, NULL,
2182 (do_proto ? *list_this_part : NULL),
2183 (do_proto ? *list_this_pref : NULL),
2184 (do_proto ? *list_this_suff : NULL),
2187 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2188 Dump_RFC822HeadersBody(
2197 if (headers_only == HEADERS_ONLY) {
2201 /* signify start of msg text */
2202 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2203 if (do_proto) cprintf("text\n");
2206 if (TheMessage->cm_format_type == FMT_FIXED)
2209 mode, /* how would you like that message? */
2212 /* If the message on disk is format 0 (Citadel vari-format), we
2213 * output using the formatter at 80 columns. This is the final output
2214 * form if the transfer format is RFC822, but if the transfer format
2215 * is Citadel proprietary, it'll still work, because the indentation
2216 * for new paragraphs is correct and the client will reformat the
2217 * message to the reader's screen width.
2219 if (TheMessage->cm_format_type == FMT_CITADEL) {
2220 mptr = TheMessage->cm_fields[eMesageText];
2222 if (mode == MT_MIME) {
2223 cprintf("Content-type: text/x-citadel-variformat\n\n");
2228 /* If the message on disk is format 4 (MIME), we've gotta hand it
2229 * off to the MIME parser. The client has already been told that
2230 * this message is format 1 (fixed format), so the callback function
2231 * we use will display those parts as-is.
2233 if (TheMessage->cm_format_type == FMT_RFC822) {
2234 memset(&ma, 0, sizeof(struct ma_info));
2236 if (mode == MT_MIME) {
2237 ma.use_fo_hooks = 0;
2238 strcpy(ma.chosen_part, "1");
2239 ma.chosen_pref = 9999;
2240 ma.dont_decode = CCC->msg4_dont_decode;
2241 mime_parser(mptr, NULL,
2242 *choose_preferred, *fixed_output_pre,
2243 *fixed_output_post, (void *)&ma, 1);
2244 mime_parser(mptr, NULL,
2245 *output_preferred, NULL, NULL, (void *)&ma, 1);
2248 ma.use_fo_hooks = 1;
2249 mime_parser(mptr, NULL,
2250 *fixed_output, *fixed_output_pre,
2251 *fixed_output_post, (void *)&ma, 0);
2256 DONE: /* now we're done */
2257 if (do_proto) cprintf("000\n");
2262 * Save one or more message pointers into a specified room
2263 * (Returns 0 for success, nonzero for failure)
2264 * roomname may be NULL to use the current room
2266 * Note that the 'supplied_msg' field may be set to NULL, in which case
2267 * the message will be fetched from disk, by number, if we need to perform
2268 * replication checks. This adds an additional database read, so if the
2269 * caller already has the message in memory then it should be supplied. (Obviously
2270 * this mode of operation only works if we're saving a single message.)
2272 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2273 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2275 struct CitContext *CCC = CC;
2277 char hold_rm[ROOMNAMELEN];
2278 struct cdbdata *cdbfr;
2281 long highest_msg = 0L;
2284 struct CtdlMessage *msg = NULL;
2286 long *msgs_to_be_merged = NULL;
2287 int num_msgs_to_be_merged = 0;
2289 MSG_syslog(LOG_DEBUG,
2290 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2291 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2294 strcpy(hold_rm, CCC->room.QRname);
2297 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2298 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2299 if (num_newmsgs > 1) supplied_msg = NULL;
2301 /* Now the regular stuff */
2302 if (CtdlGetRoomLock(&CCC->room,
2303 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2305 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2306 return(ERROR + ROOM_NOT_FOUND);
2310 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2311 num_msgs_to_be_merged = 0;
2314 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2315 if (cdbfr == NULL) {
2319 msglist = (long *) cdbfr->ptr;
2320 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2321 num_msgs = cdbfr->len / sizeof(long);
2326 /* Create a list of msgid's which were supplied by the caller, but do
2327 * not already exist in the target room. It is absolutely taboo to
2328 * have more than one reference to the same message in a room.
2330 for (i=0; i<num_newmsgs; ++i) {
2332 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2333 if (msglist[j] == newmsgidlist[i]) {
2338 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2342 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2345 * Now merge the new messages
2347 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2348 if (msglist == NULL) {
2349 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2350 free(msgs_to_be_merged);
2351 return (ERROR + INTERNAL_ERROR);
2353 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2354 num_msgs += num_msgs_to_be_merged;
2356 /* Sort the message list, so all the msgid's are in order */
2357 num_msgs = sort_msglist(msglist, num_msgs);
2359 /* Determine the highest message number */
2360 highest_msg = msglist[num_msgs - 1];
2362 /* Write it back to disk. */
2363 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2364 msglist, (int)(num_msgs * sizeof(long)));
2366 /* Free up the memory we used. */
2369 /* Update the highest-message pointer and unlock the room. */
2370 CCC->room.QRhighest = highest_msg;
2371 CtdlPutRoomLock(&CCC->room);
2373 /* Perform replication checks if necessary */
2374 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2375 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2377 for (i=0; i<num_msgs_to_be_merged; ++i) {
2378 msgid = msgs_to_be_merged[i];
2380 if (supplied_msg != NULL) {
2384 msg = CtdlFetchMessage(msgid, 0);
2388 ReplicationChecks(msg);
2390 /* If the message has an Exclusive ID, index that... */
2391 if (!CM_IsEmpty(msg, eExclusiveID)) {
2392 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
2395 /* Free up the memory we may have allocated */
2396 if (msg != supplied_msg) {
2405 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2408 /* Submit this room for processing by hooks */
2409 PerformRoomHooks(&CCC->room);
2411 /* Go back to the room we were in before we wandered here... */
2412 CtdlGetRoom(&CCC->room, hold_rm);
2414 /* Bump the reference count for all messages which were merged */
2415 if (!suppress_refcount_adj) {
2416 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2419 /* Free up memory... */
2420 if (msgs_to_be_merged != NULL) {
2421 free(msgs_to_be_merged);
2424 /* Return success. */
2430 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2433 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2434 int do_repl_check, struct CtdlMessage *supplied_msg)
2436 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2443 * Message base operation to save a new message to the message store
2444 * (returns new message number)
2446 * This is the back end for CtdlSubmitMsg() and should not be directly
2447 * called by server-side modules.
2450 long send_message(struct CtdlMessage *msg) {
2451 struct CitContext *CCC = CC;
2461 /* Get a new message number */
2462 newmsgid = get_new_message_number();
2463 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2464 (long unsigned int) time(NULL),
2465 (long unsigned int) newmsgid,
2469 /* Generate an ID if we don't have one already */
2470 if (CM_IsEmpty(msg, emessageId)) {
2471 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2474 /* If the message is big, set its body aside for storage elsewhere */
2475 if (!CM_IsEmpty(msg, eMesageText)) {
2476 if (msg->cm_lengths[eMesageText] > BIGMSG) {
2478 holdM = msg->cm_fields[eMesageText];
2479 msg->cm_fields[eMesageText] = NULL;
2480 oldMLen = msg->cm_lengths[eMesageText];
2481 msg->cm_lengths[eMesageText] = 0;
2485 /* Serialize our data structure for storage in the database */
2486 CtdlSerializeMessage(&smr, msg);
2489 msg->cm_fields[eMesageText] = holdM;
2493 cprintf("%d Unable to serialize message\n",
2494 ERROR + INTERNAL_ERROR);
2498 /* Write our little bundle of joy into the message base */
2499 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2500 smr.ser, smr.len) < 0) {
2501 MSGM_syslog(LOG_ERR, "Can't store message\n");
2505 cdb_store(CDB_BIGMSGS,
2515 /* Free the memory we used for the serialized message */
2518 /* Return the *local* message ID to the caller
2519 * (even if we're storing an incoming network message)
2527 * Serialize a struct CtdlMessage into the format used on disk and network.
2529 * This function loads up a "struct ser_ret" (defined in server.h) which
2530 * contains the length of the serialized message and a pointer to the
2531 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2533 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2534 struct CtdlMessage *msg) /* unserialized msg */
2536 struct CitContext *CCC = CC;
2541 * Check for valid message format
2543 if (CM_IsValidMsg(msg) == 0) {
2544 MSGM_syslog(LOG_ERR, "CtdlSerializeMessage() aborting due to invalid message\n");
2551 for (i=0; i < NDiskFields; ++i)
2552 if (msg->cm_fields[FieldOrder[i]] != NULL)
2553 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2555 ret->ser = malloc(ret->len);
2556 if (ret->ser == NULL) {
2557 MSG_syslog(LOG_ERR, "CtdlSerializeMessage() malloc(%ld) failed: %s\n",
2558 (long)ret->len, strerror(errno));
2565 ret->ser[1] = msg->cm_anon_type;
2566 ret->ser[2] = msg->cm_format_type;
2569 for (i=0; i < NDiskFields; ++i)
2570 if (msg->cm_fields[FieldOrder[i]] != NULL)
2572 ret->ser[wlen++] = (char)FieldOrder[i];
2574 memcpy(&ret->ser[wlen],
2575 msg->cm_fields[FieldOrder[i]],
2576 msg->cm_lengths[FieldOrder[i]] + 1);
2578 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2581 if (ret->len != wlen) {
2582 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2583 (long)ret->len, (long)wlen);
2591 * Check to see if any messages already exist in the current room which
2592 * carry the same Exclusive ID as this one. If any are found, delete them.
2594 void ReplicationChecks(struct CtdlMessage *msg) {
2595 struct CitContext *CCC = CC;
2596 long old_msgnum = (-1L);
2598 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
2600 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2603 /* No exclusive id? Don't do anything. */
2604 if (msg == NULL) return;
2605 if (CM_IsEmpty(msg, eExclusiveID)) return;
2607 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2608 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
2610 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
2611 if (old_msgnum > 0L) {
2612 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2613 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
2620 * Save a message to disk and submit it into the delivery system.
2622 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2623 recptypes *recps, /* recipients (if mail) */
2624 const char *force, /* force a particular room? */
2625 int flags /* should the message be exported clean? */
2628 char hold_rm[ROOMNAMELEN];
2629 char actual_rm[ROOMNAMELEN];
2630 char force_room[ROOMNAMELEN];
2631 char content_type[SIZ]; /* We have to learn this */
2632 char recipient[SIZ];
2633 char bounce_to[1024];
2636 const char *mptr = NULL;
2637 struct ctdluser userbuf;
2639 struct MetaData smi;
2640 char *collected_addresses = NULL;
2641 struct addresses_to_be_filed *aptr = NULL;
2642 StrBuf *saved_rfc822_version = NULL;
2643 int qualified_for_journaling = 0;
2644 CitContext *CCC = MyContext();
2646 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2647 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2649 /* If this message has no timestamp, we take the liberty of
2650 * giving it one, right now.
2652 if (CM_IsEmpty(msg, eTimestamp)) {
2653 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2656 /* If this message has no path, we generate one.
2658 if (CM_IsEmpty(msg, eMessagePath)) {
2659 if (!CM_IsEmpty(msg, eAuthor)) {
2660 CM_CopyField(msg, eMessagePath, eAuthor);
2661 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2662 if (isspace(msg->cm_fields[eMessagePath][a])) {
2663 msg->cm_fields[eMessagePath][a] = ' ';
2668 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2672 if (force == NULL) {
2673 force_room[0] = '\0';
2676 strcpy(force_room, force);
2679 /* Learn about what's inside, because it's what's inside that counts */
2680 if (CM_IsEmpty(msg, eMesageText)) {
2681 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2685 switch (msg->cm_format_type) {
2687 strcpy(content_type, "text/x-citadel-variformat");
2690 strcpy(content_type, "text/plain");
2693 strcpy(content_type, "text/plain");
2694 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2697 safestrncpy(content_type, &mptr[13], sizeof content_type);
2698 striplt(content_type);
2699 aptr = content_type;
2700 while (!IsEmptyStr(aptr)) {
2712 /* Goto the correct room */
2713 room = (recps) ? CCC->room.QRname : SENTITEMS;
2714 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
2715 strcpy(hold_rm, CCC->room.QRname);
2716 strcpy(actual_rm, CCC->room.QRname);
2717 if (recps != NULL) {
2718 strcpy(actual_rm, SENTITEMS);
2721 /* If the user is a twit, move to the twit room for posting */
2723 if (CCC->user.axlevel == AxProbU) {
2724 strcpy(hold_rm, actual_rm);
2725 strcpy(actual_rm, config.c_twitroom);
2726 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
2730 /* ...or if this message is destined for Aide> then go there. */
2731 if (!IsEmptyStr(force_room)) {
2732 strcpy(actual_rm, force_room);
2735 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
2736 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2737 /* CtdlGetRoom(&CCC->room, actual_rm); */
2738 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2742 * If this message has no O (room) field, generate one.
2744 if (CM_IsEmpty(msg, eOriginalRoom)) {
2745 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
2748 /* Perform "before save" hooks (aborting if any return nonzero) */
2749 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
2750 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2753 * If this message has an Exclusive ID, and the room is replication
2754 * checking enabled, then do replication checks.
2756 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2757 ReplicationChecks(msg);
2760 /* Save it to disk */
2761 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
2762 newmsgid = send_message(msg);
2763 if (newmsgid <= 0L) return(-5);
2765 /* Write a supplemental message info record. This doesn't have to
2766 * be a critical section because nobody else knows about this message
2769 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
2770 memset(&smi, 0, sizeof(struct MetaData));
2771 smi.meta_msgnum = newmsgid;
2772 smi.meta_refcount = 0;
2773 safestrncpy(smi.meta_content_type, content_type,
2774 sizeof smi.meta_content_type);
2777 * Measure how big this message will be when rendered as RFC822.
2778 * We do this for two reasons:
2779 * 1. We need the RFC822 length for the new metadata record, so the
2780 * POP and IMAP services don't have to calculate message lengths
2781 * while the user is waiting (multiplied by potentially hundreds
2782 * or thousands of messages).
2783 * 2. If journaling is enabled, we will need an RFC822 version of the
2784 * message to attach to the journalized copy.
2786 if (CCC->redirect_buffer != NULL) {
2787 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2790 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2791 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2792 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
2793 saved_rfc822_version = CCC->redirect_buffer;
2794 CCC->redirect_buffer = NULL;
2798 /* Now figure out where to store the pointers */
2799 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
2801 /* If this is being done by the networker delivering a private
2802 * message, we want to BYPASS saving the sender's copy (because there
2803 * is no local sender; it would otherwise go to the Trashcan).
2805 if ((!CCC->internal_pgm) || (recps == NULL)) {
2806 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2807 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
2808 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2812 /* For internet mail, drop a copy in the outbound queue room */
2813 if ((recps != NULL) && (recps->num_internet > 0)) {
2814 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2817 /* If other rooms are specified, drop them there too. */
2818 if ((recps != NULL) && (recps->num_room > 0))
2819 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2820 extract_token(recipient, recps->recp_room, i,
2821 '|', sizeof recipient);
2822 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
2823 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2826 /* Bump this user's messages posted counter. */
2827 MSGM_syslog(LOG_DEBUG, "Updating user\n");
2828 CtdlGetUserLock(&CCC->user, CCC->curr_user);
2829 CCC->user.posted = CCC->user.posted + 1;
2830 CtdlPutUserLock(&CCC->user);
2832 /* Decide where bounces need to be delivered */
2833 if ((recps != NULL) && (recps->bounce_to == NULL))
2836 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2837 CCC->user.fullname, config.c_nodename);
2839 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2840 msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
2841 recps->bounce_to = bounce_to;
2844 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2847 /* If this is private, local mail, make a copy in the
2848 * recipient's mailbox and bump the reference count.
2850 if ((recps != NULL) && (recps->num_local > 0))
2855 pch = recps->recp_local;
2856 recps->recp_local = recipient;
2857 ntokens = num_tokens(pch, '|');
2858 for (i=0; i<ntokens; ++i)
2860 extract_token(recipient, pch, i, '|', sizeof recipient);
2861 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n", recipient);
2862 if (CtdlGetUser(&userbuf, recipient) == 0) {
2863 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2864 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2865 CtdlBumpNewMailCounter(userbuf.usernum);
2866 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2869 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
2870 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2873 recps->recp_local = pch;
2876 /* Perform "after save" hooks */
2877 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
2879 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2880 CM_FlushField(msg, eVltMsgNum);
2882 /* Go back to the room we started from */
2883 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
2884 if (strcasecmp(hold_rm, CCC->room.QRname))
2885 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
2888 * Any addresses to harvest for someone's address book?
2890 if ( (CCC->logged_in) && (recps != NULL) ) {
2891 collected_addresses = harvest_collected_addresses(msg);
2894 if (collected_addresses != NULL) {
2895 aptr = (struct addresses_to_be_filed *)
2896 malloc(sizeof(struct addresses_to_be_filed));
2897 CtdlMailboxName(actual_rm, sizeof actual_rm,
2898 &CCC->user, USERCONTACTSROOM);
2899 aptr->roomname = strdup(actual_rm);
2900 aptr->collected_addresses = collected_addresses;
2901 begin_critical_section(S_ATBF);
2904 end_critical_section(S_ATBF);
2908 * Determine whether this message qualifies for journaling.
2910 if (!CM_IsEmpty(msg, eJournal)) {
2911 qualified_for_journaling = 0;
2914 if (recps == NULL) {
2915 qualified_for_journaling = config.c_journal_pubmsgs;
2917 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2918 qualified_for_journaling = config.c_journal_email;
2921 qualified_for_journaling = config.c_journal_pubmsgs;
2926 * Do we have to perform journaling? If so, hand off the saved
2927 * RFC822 version will be handed off to the journaler for background
2928 * submit. Otherwise, we have to free the memory ourselves.
2930 if (saved_rfc822_version != NULL) {
2931 if (qualified_for_journaling) {
2932 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2935 FreeStrBuf(&saved_rfc822_version);
2939 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2940 recps->bounce_to = NULL;
2948 * Convenience function for generating small administrative messages.
2950 void quickie_message(const char *from,
2951 const char *fromaddr,
2956 const char *subject)
2958 struct CtdlMessage *msg;
2959 recptypes *recp = NULL;
2961 msg = malloc(sizeof(struct CtdlMessage));
2962 memset(msg, 0, sizeof(struct CtdlMessage));
2963 msg->cm_magic = CTDLMESSAGE_MAGIC;
2964 msg->cm_anon_type = MES_NORMAL;
2965 msg->cm_format_type = format_type;
2968 msg->cm_fields[eAuthor] = strdup(from);
2970 else if (fromaddr != NULL) {
2971 msg->cm_fields[eAuthor] = strdup(fromaddr);
2972 if (strchr(msg->cm_fields[eAuthor], '@')) {
2973 *strchr(msg->cm_fields[eAuthor], '@') = 0;
2977 msg->cm_fields[eAuthor] = strdup("Citadel");
2980 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
2981 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
2982 msg->cm_fields[eNodeName] = strdup(NODENAME);
2984 msg->cm_fields[eRecipient] = strdup(to);
2985 recp = validate_recipients(to, NULL, 0);
2987 if (subject != NULL) {
2988 msg->cm_fields[eMsgSubject] = strdup(subject);
2990 msg->cm_fields[eMesageText] = strdup(text);
2992 CtdlSubmitMsg(msg, recp, room, 0);
2994 if (recp != NULL) free_recipients(recp);
2997 void flood_protect_quickie_message(const char *from,
2998 const char *fromaddr,
3003 const char *subject,
3005 const char **CritStr,
3012 u_char rawdigest[MD5_DIGEST_LEN];
3013 struct MD5Context md5context;
3017 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3019 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3020 MD5Init(&md5context);
3022 for (i = 0; i < nCriterions; i++)
3023 MD5Update(&md5context,
3024 (const unsigned char*)CritStr[i], CritStrLen[i]);
3025 MD5Update(&md5context,
3026 (const unsigned char*)timestamp, tslen);
3027 MD5Final(rawdigest, &md5context);
3029 guid = NewStrBufPlain(NULL,
3030 MD5_DIGEST_LEN * 2 + 12);
3031 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3032 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3033 if (StrLength(guid) > 40)
3034 StrBufCutAt(guid, 40, NULL);
3036 if (CheckIfAlreadySeen("FPAideMessage",
3045 /* yes, we did. flood protection kicks in. */
3047 "not sending message again\n");
3051 /* no, this message isn't sent recently; go ahead. */
3052 quickie_message(from,
3063 * Back end function used by CtdlMakeMessage() and similar functions
3065 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3067 size_t maxlen, /* maximum message length */
3068 StrBuf *exist, /* if non-null, append to it;
3069 exist is ALWAYS freed */
3070 int crlf, /* CRLF newlines instead of LF */
3071 int *sock /* socket handle or 0 for this session's client socket */
3080 LineBuf = NewStrBufPlain(NULL, SIZ);
3081 if (exist == NULL) {
3082 Message = NewStrBufPlain(NULL, 4 * SIZ);
3085 Message = NewStrBufDup(exist);
3088 /* Do we need to change leading ".." to "." for SMTP escaping? */
3089 if ((tlen == 1) && (*terminator == '.')) {
3093 /* read in the lines of message text one by one */
3096 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3101 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3103 if ((StrLength(LineBuf) == tlen) &&
3104 (!strcmp(ChrPtr(LineBuf), terminator)))
3107 if ( (!flushing) && (!finished) ) {
3109 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3112 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3115 /* Unescape SMTP-style input of two dots at the beginning of the line */
3117 (StrLength(LineBuf) == 2) &&
3118 (!strcmp(ChrPtr(LineBuf), "..")))
3120 StrBufCutLeft(LineBuf, 1);
3123 StrBufAppendBuf(Message, LineBuf, 0);
3126 /* if we've hit the max msg length, flush the rest */
3127 if (StrLength(Message) >= maxlen) flushing = 1;
3129 } while (!finished);
3130 FreeStrBuf(&LineBuf);
3134 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3138 FreeStrBuf(&(*Msg)->MsgBuf);
3144 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3146 size_t maxlen, /* maximum message length */
3147 size_t expectlen, /* if we expect a message, how long should it be? */
3148 StrBuf *exist, /* if non-null, append to it;
3149 exist is ALWAYS freed */
3150 long eLen, /* length of exist */
3151 int crlf /* CRLF newlines instead of LF */
3154 ReadAsyncMsg *NewMsg;
3156 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3157 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3159 if (exist == NULL) {
3162 if (expectlen == 0) {
3166 len = expectlen + 10;
3168 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3171 NewMsg->MsgBuf = NewStrBufDup(exist);
3173 /* Do we need to change leading ".." to "." for SMTP escaping? */
3174 if ((tlen == 1) && (*terminator == '.')) {
3178 NewMsg->terminator = terminator;
3179 NewMsg->tlen = tlen;
3181 NewMsg->maxlen = maxlen;
3183 NewMsg->crlf = crlf;
3189 * Back end function used by CtdlMakeMessage() and similar functions
3191 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3193 ReadAsyncMsg *ReadMsg;
3194 int MsgFinished = 0;
3195 eReadState Finished = eMustReadMore;
3200 const char *pch = ChrPtr(IO->SendBuf.Buf);
3201 const char *pchh = IO->SendBuf.ReadWritePointer;
3207 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3208 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3209 ((CitContext*)(IO->CitContext))->ServiceName,
3212 fd = fopen(fn, "a+");
3215 ReadMsg = IO->ReadMsg;
3217 /* read in the lines of message text one by one */
3219 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3222 case eMustReadMore: /// read new from socket...
3224 if (IO->RecvBuf.ReadWritePointer != NULL) {
3225 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3226 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3228 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3232 fprintf(fd, "BufferEmpty! \n");
3238 case eBufferNotEmpty: /* shouldn't happen... */
3239 case eReadSuccess: /// done for now...
3241 case eReadFail: /// WHUT?
3247 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3248 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3251 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3254 else if (!ReadMsg->flushing) {
3257 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3260 /* Unescape SMTP-style input of two dots at the beginning of the line */
3261 if ((ReadMsg->dodot) &&
3262 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3263 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3266 fprintf(fd, "UnEscaped!\n");
3268 StrBufCutLeft(IO->IOBuf, 1);
3271 if (ReadMsg->crlf) {
3272 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3275 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3278 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3281 /* if we've hit the max msg length, flush the rest */
3282 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3284 } while (!MsgFinished);
3287 fprintf(fd, "Done with reading; %s.\n, ",
3288 (MsgFinished)?"Message Finished": "FAILED");
3292 return eReadSuccess;
3299 * Back end function used by CtdlMakeMessage() and similar functions
3301 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3303 size_t maxlen, /* maximum message length */
3304 StrBuf *exist, /* if non-null, append to it;
3305 exist is ALWAYS freed */
3306 int crlf, /* CRLF newlines instead of LF */
3307 int *sock /* socket handle or 0 for this session's client socket */
3312 Message = CtdlReadMessageBodyBuf(terminator,
3318 if (Message == NULL)
3321 return SmashStrBuf(&Message);
3324 struct CtdlMessage *CtdlMakeMessage(
3325 struct ctdluser *author, /* author's user structure */
3326 char *recipient, /* NULL if it's not mail */
3327 char *recp_cc, /* NULL if it's not mail */
3328 char *room, /* room where it's going */
3329 int type, /* see MES_ types in header file */
3330 int format_type, /* variformat, plain text, MIME... */
3331 char *fake_name, /* who we're masquerading as */
3332 char *my_email, /* which of my email addresses to use (empty is ok) */
3333 char *subject, /* Subject (optional) */
3334 char *supplied_euid, /* ...or NULL if this is irrelevant */
3335 char *preformatted_text, /* ...or NULL to read text from client */
3336 char *references /* Thread references */
3339 return CtdlMakeMessageLen(
3340 author, /* author's user structure */
3341 recipient, /* NULL if it's not mail */
3342 (recipient)?strlen(recipient) : 0,
3343 recp_cc, /* NULL if it's not mail */
3344 (recp_cc)?strlen(recp_cc): 0,
3345 room, /* room where it's going */
3346 (room)?strlen(room): 0,
3347 type, /* see MES_ types in header file */
3348 format_type, /* variformat, plain text, MIME... */
3349 fake_name, /* who we're masquerading as */
3350 (fake_name)?strlen(fake_name): 0,
3351 my_email, /* which of my email addresses to use (empty is ok) */
3352 (my_email)?strlen(my_email): 0,
3353 subject, /* Subject (optional) */
3354 (subject)?strlen(subject): 0,
3355 supplied_euid, /* ...or NULL if this is irrelevant */
3356 (supplied_euid)?strlen(supplied_euid):0,
3357 preformatted_text, /* ...or NULL to read text from client */
3358 (preformatted_text)?strlen(preformatted_text) : 0,
3359 references, /* Thread references */
3360 (references)?strlen(references):0);
3365 * Build a binary message to be saved on disk.
3366 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3367 * will become part of the message. This means you are no longer
3368 * responsible for managing that memory -- it will be freed along with
3369 * the rest of the fields when CM_Free() is called.)
3372 struct CtdlMessage *CtdlMakeMessageLen(
3373 struct ctdluser *author, /* author's user structure */
3374 char *recipient, /* NULL if it's not mail */
3376 char *recp_cc, /* NULL if it's not mail */
3378 char *room, /* room where it's going */
3380 int type, /* see MES_ types in header file */
3381 int format_type, /* variformat, plain text, MIME... */
3382 char *fake_name, /* who we're masquerading as */
3384 char *my_email, /* which of my email addresses to use (empty is ok) */
3386 char *subject, /* Subject (optional) */
3388 char *supplied_euid, /* ...or NULL if this is irrelevant */
3390 char *preformatted_text, /* ...or NULL to read text from client */
3392 char *references, /* Thread references */
3396 struct CitContext *CCC = CC;
3397 /* Don't confuse the poor folks if it's not routed mail. * /
3398 char dest_node[256] = "";*/
3401 struct CtdlMessage *msg;
3403 StrBuf *FakeEncAuthor = NULL;
3405 msg = malloc(sizeof(struct CtdlMessage));
3406 memset(msg, 0, sizeof(struct CtdlMessage));
3407 msg->cm_magic = CTDLMESSAGE_MAGIC;
3408 msg->cm_anon_type = type;
3409 msg->cm_format_type = format_type;
3411 if (recipient != NULL) rcplen = striplt(recipient);
3412 if (recp_cc != NULL) cclen = striplt(recp_cc);
3414 /* Path or Return-Path */
3416 CM_SetField(msg, eMessagePath, my_email, myelen);
3419 CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
3421 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3423 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3424 CM_SetField(msg, eTimestamp, buf, blen);
3427 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3430 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3432 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3433 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3435 if (CCC->room.QRflags & QR_MAILBOX) { /* room */
3436 CM_SetField(msg, eOriginalRoom, &CCC->room.QRname[11], strlen(&CCC->room.QRname[11]));
3439 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3442 CM_SetField(msg, eNodeName, NODENAME, strlen(NODENAME));
3443 CM_SetField(msg, eHumanNode, HUMANNODE, strlen(HUMANNODE));
3446 CM_SetField(msg, eRecipient, recipient, rcplen);
3449 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3453 CM_SetField(msg, erFc822Addr, my_email, myelen);
3455 else if ( (author == &CCC->user) && (!IsEmptyStr(CCC->cs_inet_email)) ) {
3456 CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email));
3459 if (subject != NULL) {
3461 length = striplt(subject);
3467 while ((subject[i] != '\0') &&
3468 (IsAscii = isascii(subject[i]) != 0 ))
3471 CM_SetField(msg, eMsgSubject, subject, subjlen);
3472 else /* ok, we've got utf8 in the string. */
3475 rfc2047Subj = rfc2047encode(subject, length);
3476 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3483 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3487 CM_SetField(msg, eWeferences, references, reflen);
3490 if (preformatted_text != NULL) {
3491 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3494 preformatted_text = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3495 if (preformatted_text != NULL) {
3496 CM_SetField(msg, eMesageText, preformatted_text, strlen(preformatted_text));
3507 * API function to delete messages which match a set of criteria
3508 * (returns the actual number of messages deleted)
3510 int CtdlDeleteMessages(char *room_name, /* which room */
3511 long *dmsgnums, /* array of msg numbers to be deleted */
3512 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3513 char *content_type /* or "" for any. regular expressions expected. */
3516 struct CitContext *CCC = CC;
3517 struct ctdlroom qrbuf;
3518 struct cdbdata *cdbfr;
3519 long *msglist = NULL;
3520 long *dellist = NULL;
3523 int num_deleted = 0;
3525 struct MetaData smi;
3528 int need_to_free_re = 0;
3530 if (content_type) if (!IsEmptyStr(content_type)) {
3531 regcomp(&re, content_type, 0);
3532 need_to_free_re = 1;
3534 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
3535 room_name, num_dmsgnums, content_type);
3537 /* get room record, obtaining a lock... */
3538 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3539 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
3541 if (need_to_free_re) regfree(&re);
3542 return (0); /* room not found */
3544 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3546 if (cdbfr != NULL) {
3547 dellist = malloc(cdbfr->len);
3548 msglist = (long *) cdbfr->ptr;
3549 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3550 num_msgs = cdbfr->len / sizeof(long);
3554 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3555 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3556 int have_more_del = 1;
3558 num_msgs = sort_msglist(msglist, num_msgs);
3559 if (num_dmsgnums > 1)
3560 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3563 StrBuf *dbg = NewStrBuf();
3564 for (i = 0; i < num_dmsgnums; i++)
3565 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3566 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
3571 while ((i < num_msgs) && (have_more_del)) {
3574 /* Set/clear a bit for each criterion */
3576 /* 0 messages in the list or a null list means that we are
3577 * interested in deleting any messages which meet the other criteria.
3580 delete_this |= 0x01;
3583 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3588 if (msglist[i] == dmsgnums[j]) {
3589 delete_this |= 0x01;
3592 have_more_del = (j < num_dmsgnums);
3595 if (have_contenttype) {
3596 GetMetaData(&smi, msglist[i]);
3597 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3598 delete_this |= 0x02;
3601 delete_this |= 0x02;
3604 /* Delete message only if all bits are set */
3605 if (delete_this == 0x03) {
3606 dellist[num_deleted++] = msglist[i];
3613 StrBuf *dbg = NewStrBuf();
3614 for (i = 0; i < num_deleted; i++)
3615 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3616 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
3620 num_msgs = sort_msglist(msglist, num_msgs);
3621 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3622 msglist, (int)(num_msgs * sizeof(long)));
3625 qrbuf.QRhighest = msglist[num_msgs - 1];
3627 qrbuf.QRhighest = 0;
3629 CtdlPutRoomLock(&qrbuf);
3631 /* Go through the messages we pulled out of the index, and decrement
3632 * their reference counts by 1. If this is the only room the message
3633 * was in, the reference count will reach zero and the message will
3634 * automatically be deleted from the database. We do this in a
3635 * separate pass because there might be plug-in hooks getting called,
3636 * and we don't want that happening during an S_ROOMS critical
3640 for (i=0; i<num_deleted; ++i) {
3641 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3643 AdjRefCountList(dellist, num_deleted, -1);
3645 /* Now free the memory we used, and go away. */
3646 if (msglist != NULL) free(msglist);
3647 if (dellist != NULL) free(dellist);
3648 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
3649 if (need_to_free_re) regfree(&re);
3650 return (num_deleted);
3657 * GetMetaData() - Get the supplementary record for a message
3659 void GetMetaData(struct MetaData *smibuf, long msgnum)
3662 struct cdbdata *cdbsmi;
3665 memset(smibuf, 0, sizeof(struct MetaData));
3666 smibuf->meta_msgnum = msgnum;
3667 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3669 /* Use the negative of the message number for its supp record index */
3670 TheIndex = (0L - msgnum);
3672 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3673 if (cdbsmi == NULL) {
3674 return; /* record not found; go with defaults */
3676 memcpy(smibuf, cdbsmi->ptr,
3677 ((cdbsmi->len > sizeof(struct MetaData)) ?
3678 sizeof(struct MetaData) : cdbsmi->len));
3685 * PutMetaData() - (re)write supplementary record for a message
3687 void PutMetaData(struct MetaData *smibuf)
3691 /* Use the negative of the message number for the metadata db index */
3692 TheIndex = (0L - smibuf->meta_msgnum);
3694 cdb_store(CDB_MSGMAIN,
3695 &TheIndex, (int)sizeof(long),
3696 smibuf, (int)sizeof(struct MetaData));
3701 * AdjRefCount - submit an adjustment to the reference count for a message.
3702 * (These are just queued -- we actually process them later.)
3704 void AdjRefCount(long msgnum, int incr)
3706 struct CitContext *CCC = CC;
3707 struct arcq new_arcq;
3710 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
3712 begin_critical_section(S_SUPPMSGMAIN);
3713 if (arcfp == NULL) {
3714 arcfp = fopen(file_arcq, "ab+");
3715 chown(file_arcq, CTDLUID, (-1));
3716 chmod(file_arcq, 0600);
3718 end_critical_section(S_SUPPMSGMAIN);
3720 /* msgnum < 0 means that we're trying to close the file */
3722 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
3723 begin_critical_section(S_SUPPMSGMAIN);
3724 if (arcfp != NULL) {
3728 end_critical_section(S_SUPPMSGMAIN);
3733 * If we can't open the queue, perform the operation synchronously.
3735 if (arcfp == NULL) {
3736 TDAP_AdjRefCount(msgnum, incr);
3740 new_arcq.arcq_msgnum = msgnum;
3741 new_arcq.arcq_delta = incr;
3742 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3744 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3753 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3755 struct CitContext *CCC = CC;
3756 long i, the_size, offset;
3757 struct arcq *new_arcq;
3760 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
3762 begin_critical_section(S_SUPPMSGMAIN);
3763 if (arcfp == NULL) {
3764 arcfp = fopen(file_arcq, "ab+");
3765 chown(file_arcq, CTDLUID, (-1));
3766 chmod(file_arcq, 0600);
3768 end_critical_section(S_SUPPMSGMAIN);
3771 * If we can't open the queue, perform the operation synchronously.
3773 if (arcfp == NULL) {
3774 for (i = 0; i < nmsg; i++)
3775 TDAP_AdjRefCount(msgnum[i], incr);
3779 the_size = sizeof(struct arcq) * nmsg;
3780 new_arcq = malloc(the_size);
3781 for (i = 0; i < nmsg; i++) {
3782 new_arcq[i].arcq_msgnum = msgnum[i];
3783 new_arcq[i].arcq_delta = incr;
3787 while ((rv >= 0) && (offset < the_size))
3789 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
3791 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3807 * TDAP_ProcessAdjRefCountQueue()
3809 * Process the queue of message count adjustments that was created by calls
3810 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3811 * for each one. This should be an "off hours" operation.
3813 int TDAP_ProcessAdjRefCountQueue(void)
3815 struct CitContext *CCC = CC;
3816 char file_arcq_temp[PATH_MAX];
3819 struct arcq arcq_rec;
3820 int num_records_processed = 0;
3822 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
3824 begin_critical_section(S_SUPPMSGMAIN);
3825 if (arcfp != NULL) {
3830 r = link(file_arcq, file_arcq_temp);
3832 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3833 end_critical_section(S_SUPPMSGMAIN);
3834 return(num_records_processed);
3838 end_critical_section(S_SUPPMSGMAIN);
3840 fp = fopen(file_arcq_temp, "rb");
3842 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3843 return(num_records_processed);
3846 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3847 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3848 ++num_records_processed;
3852 r = unlink(file_arcq_temp);
3854 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3857 return(num_records_processed);
3863 * TDAP_AdjRefCount - adjust the reference count for a message.
3864 * This one does it "for real" because it's called by
3865 * the autopurger function that processes the queue
3866 * created by AdjRefCount(). If a message's reference
3867 * count becomes zero, we also delete the message from
3868 * disk and de-index it.
3870 void TDAP_AdjRefCount(long msgnum, int incr)
3872 struct CitContext *CCC = CC;
3874 struct MetaData smi;
3877 /* This is a *tight* critical section; please keep it that way, as
3878 * it may get called while nested in other critical sections.
3879 * Complicating this any further will surely cause deadlock!
3881 begin_critical_section(S_SUPPMSGMAIN);
3882 GetMetaData(&smi, msgnum);
3883 smi.meta_refcount += incr;
3885 end_critical_section(S_SUPPMSGMAIN);
3886 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
3887 msgnum, incr, smi.meta_refcount
3890 /* If the reference count is now zero, delete the message
3891 * (and its supplementary record as well).
3893 if (smi.meta_refcount == 0) {
3894 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
3896 /* Call delete hooks with NULL room to show it has gone altogether */
3897 PerformDeleteHooks(NULL, msgnum);
3899 /* Remove from message base */
3901 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3902 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3904 /* Remove metadata record */
3905 delnum = (0L - msgnum);
3906 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3912 * Write a generic object to this room
3914 * Note: this could be much more efficient. Right now we use two temporary
3915 * files, and still pull the message into memory as with all others.
3917 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3918 char *content_type, /* MIME type of this object */
3919 char *raw_message, /* Data to be written */
3920 off_t raw_length, /* Size of raw_message */
3921 struct ctdluser *is_mailbox, /* Mailbox room? */
3922 int is_binary, /* Is encoding necessary? */
3923 int is_unique, /* Del others of this type? */
3924 unsigned int flags /* Internal save flags */
3927 struct CitContext *CCC = CC;
3928 struct ctdlroom qrbuf;
3929 char roomname[ROOMNAMELEN];
3930 struct CtdlMessage *msg;
3931 StrBuf *encoded_message = NULL;
3933 if (is_mailbox != NULL) {
3934 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3937 safestrncpy(roomname, req_room, sizeof(roomname));
3940 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
3943 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
3946 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
3949 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
3950 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
3951 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
3954 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
3957 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
3961 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
3964 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
3967 MSGM_syslog(LOG_DEBUG, "Allocating\n");
3968 msg = malloc(sizeof(struct CtdlMessage));
3969 memset(msg, 0, sizeof(struct CtdlMessage));
3970 msg->cm_magic = CTDLMESSAGE_MAGIC;
3971 msg->cm_anon_type = MES_NORMAL;
3972 msg->cm_format_type = 4;
3973 CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
3974 CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
3975 CM_SetField(msg, eNodeName, config.c_nodename, strlen(config.c_nodename));
3976 CM_SetField(msg, eHumanNode, config.c_humannode, strlen(config.c_humannode));
3977 msg->cm_flags = flags;
3979 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
3981 /* Create the requested room if we have to. */
3982 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
3983 CtdlCreateRoom(roomname,
3984 ( (is_mailbox != NULL) ? 5 : 3 ),
3985 "", 0, 1, 0, VIEW_BBS);
3987 /* If the caller specified this object as unique, delete all
3988 * other objects of this type that are currently in the room.
3991 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
3992 CtdlDeleteMessages(roomname, NULL, 0, content_type)
3995 /* Now write the data */
3996 CtdlSubmitMsg(msg, NULL, roomname, 0);
4002 /*****************************************************************************/
4003 /* MODULE INITIALIZATION STUFF */
4004 /*****************************************************************************/
4005 void SetMessageDebugEnabled(const int n)
4007 MessageDebugEnabled = n;
4009 CTDL_MODULE_INIT(msgbase)
4012 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
4015 /* return our Subversion id for the Log */