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 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0')
1135 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Forcefully terminating message!!\n", msgnum, with_body);
1136 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1140 * The rest is zero or more arbitrary fields. Load them in.
1141 * We're done when we encounter either a zero-length field or
1142 * have just processed the 'M' (message text) field.
1145 field_header = '\0';
1148 /* work around possibly buggy messages: */
1149 while (field_header == '\0')
1151 if (mptr >= upper_bound) {
1154 field_header = *mptr++;
1156 if (mptr >= upper_bound) {
1159 which = field_header;
1162 CM_SetField(ret, which, mptr, len);
1164 mptr += len + 1; /* advance to next field */
1166 } while ((mptr < upper_bound) && (field_header != 'M'));
1170 /* Always make sure there's something in the msg text field. If
1171 * it's NULL, the message text is most likely stored separately,
1172 * so go ahead and fetch that. Failing that, just set a dummy
1173 * body so other code doesn't barf.
1175 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1176 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1177 if (dmsgtext != NULL) {
1178 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1182 if (CM_IsEmpty(ret, eMesageText)) {
1183 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1186 /* Perform "before read" hooks (aborting if any return nonzero) */
1187 if (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0) {
1198 * Pre callback function for multipart/alternative
1200 * NOTE: this differs from the standard behavior for a reason. Normally when
1201 * displaying multipart/alternative you want to show the _last_ usable
1202 * format in the message. Here we show the _first_ one, because it's
1203 * usually text/plain. Since this set of functions is designed for text
1204 * output to non-MIME-aware clients, this is the desired behavior.
1207 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1208 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1209 char *cbid, void *cbuserdata)
1211 struct CitContext *CCC = CC;
1214 ma = (struct ma_info *)cbuserdata;
1215 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1216 if (!strcasecmp(cbtype, "multipart/alternative")) {
1220 if (!strcasecmp(cbtype, "message/rfc822")) {
1226 * Post callback function for multipart/alternative
1228 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1229 void *content, char *cbtype, char *cbcharset, size_t length,
1230 char *encoding, char *cbid, void *cbuserdata)
1232 struct CitContext *CCC = CC;
1235 ma = (struct ma_info *)cbuserdata;
1236 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1237 if (!strcasecmp(cbtype, "multipart/alternative")) {
1241 if (!strcasecmp(cbtype, "message/rfc822")) {
1247 * Inline callback function for mime parser that wants to display text
1249 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1250 void *content, char *cbtype, char *cbcharset, size_t length,
1251 char *encoding, char *cbid, void *cbuserdata)
1253 struct CitContext *CCC = CC;
1259 ma = (struct ma_info *)cbuserdata;
1261 MSG_syslog(LOG_DEBUG,
1262 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1263 partnum, filename, cbtype, (long)length);
1266 * If we're in the middle of a multipart/alternative scope and
1267 * we've already printed another section, skip this one.
1269 if ( (ma->is_ma) && (ma->did_print) ) {
1270 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1275 if ( (!strcasecmp(cbtype, "text/plain"))
1276 || (IsEmptyStr(cbtype)) ) {
1279 client_write(wptr, length);
1280 if (wptr[length-1] != '\n') {
1287 if (!strcasecmp(cbtype, "text/html")) {
1288 ptr = html_to_ascii(content, length, 80, 0);
1290 client_write(ptr, wlen);
1291 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1298 if (ma->use_fo_hooks) {
1299 if (PerformFixedOutputHooks(cbtype, content, length)) {
1300 /* above function returns nonzero if it handled the part */
1305 if (strncasecmp(cbtype, "multipart/", 10)) {
1306 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1307 partnum, filename, cbtype, (long)length);
1313 * The client is elegant and sophisticated and wants to be choosy about
1314 * MIME content types, so figure out which multipart/alternative part
1315 * we're going to send.
1317 * We use a system of weights. When we find a part that matches one of the
1318 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1319 * and then set ma->chosen_pref to that MIME type's position in our preference
1320 * list. If we then hit another match, we only replace the first match if
1321 * the preference value is lower.
1323 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1324 void *content, char *cbtype, char *cbcharset, size_t length,
1325 char *encoding, char *cbid, void *cbuserdata)
1327 struct CitContext *CCC = CC;
1332 ma = (struct ma_info *)cbuserdata;
1334 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1335 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1336 // I don't know if there are any side effects! Please TEST TEST TEST
1337 //if (ma->is_ma > 0) {
1339 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1340 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1341 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1342 if (i < ma->chosen_pref) {
1343 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1344 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1345 ma->chosen_pref = i;
1352 * Now that we've chosen our preferred part, output it.
1354 void output_preferred(char *name,
1366 struct CitContext *CCC = CC;
1369 int add_newline = 0;
1372 char *decoded = NULL;
1373 size_t bytes_decoded;
1376 ma = (struct ma_info *)cbuserdata;
1378 /* This is not the MIME part you're looking for... */
1379 if (strcasecmp(partnum, ma->chosen_part)) return;
1381 /* If the content-type of this part is in our preferred formats
1382 * list, we can simply output it verbatim.
1384 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1385 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1386 if (!strcasecmp(buf, cbtype)) {
1387 /* Yeah! Go! W00t!! */
1388 if (ma->dont_decode == 0)
1389 rc = mime_decode_now (content,
1395 break; /* Give us the chance, maybe theres another one. */
1397 if (rc == 0) text_content = (char *)content;
1399 text_content = decoded;
1400 length = bytes_decoded;
1403 if (text_content[length-1] != '\n') {
1406 cprintf("Content-type: %s", cbtype);
1407 if (!IsEmptyStr(cbcharset)) {
1408 cprintf("; charset=%s", cbcharset);
1410 cprintf("\nContent-length: %d\n",
1411 (int)(length + add_newline) );
1412 if (!IsEmptyStr(encoding)) {
1413 cprintf("Content-transfer-encoding: %s\n", encoding);
1416 cprintf("Content-transfer-encoding: 7bit\n");
1418 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1420 if (client_write(text_content, length) == -1)
1422 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1425 if (add_newline) cprintf("\n");
1426 if (decoded != NULL) free(decoded);
1431 /* No translations required or possible: output as text/plain */
1432 cprintf("Content-type: text/plain\n\n");
1434 if (ma->dont_decode == 0)
1435 rc = mime_decode_now (content,
1441 return; /* Give us the chance, maybe theres another one. */
1443 if (rc == 0) text_content = (char *)content;
1445 text_content = decoded;
1446 length = bytes_decoded;
1449 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1450 length, encoding, cbid, cbuserdata);
1451 if (decoded != NULL) free(decoded);
1456 char desired_section[64];
1463 * Callback function for
1465 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1466 void *content, char *cbtype, char *cbcharset, size_t length,
1467 char *encoding, char *cbid, void *cbuserdata)
1469 struct encapmsg *encap;
1471 encap = (struct encapmsg *)cbuserdata;
1473 /* Only proceed if this is the desired section... */
1474 if (!strcasecmp(encap->desired_section, partnum)) {
1475 encap->msglen = length;
1476 encap->msg = malloc(length + 2);
1477 memcpy(encap->msg, content, length);
1484 * Determine whether the specified message exists in the cached_msglist
1485 * (This is a security check)
1487 int check_cached_msglist(long msgnum) {
1488 struct CitContext *CCC = CC;
1490 /* cases in which we skip the check */
1491 if (!CCC) return om_ok; /* not a session */
1492 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1493 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1494 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1497 /* Do a binary search within the cached_msglist for the requested msgnum */
1499 int max = (CC->cached_num_msgs - 1);
1501 while (max >= min) {
1502 int middle = min + (max-min) / 2 ;
1503 if (msgnum == CCC->cached_msglist[middle]) {
1506 if (msgnum > CC->cached_msglist[middle]) {
1514 return om_access_denied;
1520 * Get a message off disk. (returns om_* values found in msgbase.h)
1523 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1524 int mode, /* how would you like that message? */
1525 int headers_only, /* eschew the message body? */
1526 int do_proto, /* do Citadel protocol responses? */
1527 int crlf, /* Use CRLF newlines instead of LF? */
1528 char *section, /* NULL or a message/rfc822 section */
1529 int flags, /* various flags; see msgbase.h */
1534 struct CitContext *CCC = CC;
1535 struct CtdlMessage *TheMessage = NULL;
1536 int retcode = CIT_OK;
1537 struct encapmsg encap;
1540 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1542 (section ? section : "<>")
1545 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1548 if (r == om_not_logged_in) {
1549 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1552 cprintf("%d An unknown error has occurred.\n", ERROR);
1559 * Check to make sure the message is actually IN this room
1561 r = check_cached_msglist(msg_num);
1562 if (r == om_access_denied) {
1563 /* Not in the cache? We get ONE shot to check it again. */
1564 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1565 r = check_cached_msglist(msg_num);
1568 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1569 msg_num, CCC->room.QRname
1572 if (r == om_access_denied) {
1573 cprintf("%d message %ld was not found in this room\n",
1574 ERROR + HIGHER_ACCESS_REQUIRED,
1583 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1584 * request that we don't even bother loading the body into memory.
1586 if (headers_only == HEADERS_FAST) {
1587 TheMessage = CtdlFetchMessage(msg_num, 0);
1590 TheMessage = CtdlFetchMessage(msg_num, 1);
1593 if (TheMessage == NULL) {
1594 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1595 ERROR + MESSAGE_NOT_FOUND, msg_num);
1596 return(om_no_such_msg);
1599 /* Here is the weird form of this command, to process only an
1600 * encapsulated message/rfc822 section.
1602 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1603 memset(&encap, 0, sizeof encap);
1604 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1605 mime_parser(CM_RANGE(TheMessage, eMesageText),
1606 *extract_encapsulated_message,
1607 NULL, NULL, (void *)&encap, 0
1610 if ((Author != NULL) && (*Author == NULL))
1613 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1615 if ((Address != NULL) && (*Address == NULL))
1618 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1620 if ((MessageID != NULL) && (*MessageID == NULL))
1623 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1625 CM_Free(TheMessage);
1629 encap.msg[encap.msglen] = 0;
1630 TheMessage = convert_internet_message(encap.msg);
1631 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1633 /* Now we let it fall through to the bottom of this
1634 * function, because TheMessage now contains the
1635 * encapsulated message instead of the top-level
1636 * message. Isn't that neat?
1641 cprintf("%d msg %ld has no part %s\n",
1642 ERROR + MESSAGE_NOT_FOUND,
1646 retcode = om_no_such_msg;
1651 /* Ok, output the message now */
1652 if (retcode == CIT_OK)
1653 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1654 if ((Author != NULL) && (*Author == NULL))
1657 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1659 if ((Address != NULL) && (*Address == NULL))
1662 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1664 if ((MessageID != NULL) && (*MessageID == NULL))
1667 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1670 CM_Free(TheMessage);
1677 void OutputCtdlMsgHeaders(
1678 struct CtdlMessage *TheMessage,
1679 int do_proto) /* do Citadel protocol responses? */
1684 char display_name[256];
1686 /* begin header processing loop for Citadel message format */
1687 safestrncpy(display_name, "<unknown>", sizeof display_name);
1688 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1689 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1690 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1691 safestrncpy(display_name, "****", sizeof display_name);
1693 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1694 safestrncpy(display_name, "anonymous", sizeof display_name);
1697 safestrncpy(display_name, buf, sizeof display_name);
1699 if ((is_room_aide())
1700 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1701 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1702 size_t tmp = strlen(display_name);
1703 snprintf(&display_name[tmp],
1704 sizeof display_name - tmp,
1709 /* Don't show Internet address for users on the
1710 * local Citadel network.
1713 if (!CM_IsEmpty(TheMessage, eNodeName) &&
1714 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
1719 /* Now spew the header fields in the order we like them. */
1720 for (i=0; i< NDiskFields; ++i) {
1722 Field = FieldOrder[i];
1723 if (Field != eMesageText) {
1724 if ( (!CM_IsEmpty(TheMessage, Field))
1725 && (msgkeys[Field] != NULL) ) {
1726 if ((Field == eenVelopeTo) ||
1727 (Field == eRecipient) ||
1728 (Field == eCarbonCopY)) {
1729 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1731 if (Field == eAuthor) {
1732 if (do_proto) cprintf("%s=%s\n",
1736 else if ((Field == erFc822Addr) && (suppress_f)) {
1739 /* Masquerade display name if needed */
1741 if (do_proto) cprintf("%s=%s\n",
1743 TheMessage->cm_fields[Field]
1752 void OutputRFC822MsgHeaders(
1753 struct CtdlMessage *TheMessage,
1754 int flags, /* should the bessage be exported clean */
1756 char *mid, long sizeof_mid,
1757 char *suser, long sizeof_suser,
1758 char *luser, long sizeof_luser,
1759 char *fuser, long sizeof_fuser,
1760 char *snode, long sizeof_snode)
1762 char datestamp[100];
1763 int subject_found = 0;
1770 for (i = 0; i < NDiskFields; ++i) {
1771 if (TheMessage->cm_fields[FieldOrder[i]]) {
1772 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1773 switch (FieldOrder[i]) {
1775 safestrncpy(luser, mptr, sizeof_luser);
1776 safestrncpy(suser, mptr, sizeof_suser);
1779 if ((flags & QP_EADDR) != 0) {
1780 mptr = qp_encode_email_addrs(mptr);
1782 sanitize_truncated_recipient(mptr);
1783 cprintf("CC: %s%s", mptr, nl);
1786 cprintf("Return-Path: %s%s", mptr, nl);
1789 cprintf("List-ID: %s%s", mptr, nl);
1792 if ((flags & QP_EADDR) != 0)
1793 mptr = qp_encode_email_addrs(mptr);
1795 while ((*hptr != '\0') && isspace(*hptr))
1797 if (!IsEmptyStr(hptr))
1798 cprintf("Envelope-To: %s%s", hptr, nl);
1801 cprintf("Subject: %s%s", mptr, nl);
1805 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1808 safestrncpy(fuser, mptr, sizeof_fuser);
1809 /* case eOriginalRoom:
1810 cprintf("X-Citadel-Room: %s%s",
1815 safestrncpy(snode, mptr, sizeof_snode);
1818 if (haschar(mptr, '@') == 0)
1820 sanitize_truncated_recipient(mptr);
1821 cprintf("To: %s@%s", mptr, config.c_fqdn);
1826 if ((flags & QP_EADDR) != 0) {
1827 mptr = qp_encode_email_addrs(mptr);
1829 sanitize_truncated_recipient(mptr);
1830 cprintf("To: %s", mptr);
1835 datestring(datestamp, sizeof datestamp,
1836 atol(mptr), DATESTRING_RFC822);
1837 cprintf("Date: %s%s", datestamp, nl);
1840 cprintf("References: ");
1841 k = num_tokens(mptr, '|');
1842 for (j=0; j<k; ++j) {
1843 extract_token(buf, mptr, j, '|', sizeof buf);
1844 cprintf("<%s>", buf);
1855 while ((*hptr != '\0') && isspace(*hptr))
1857 if (!IsEmptyStr(hptr))
1858 cprintf("Reply-To: %s%s", mptr, nl);
1874 /* these don't map to mime message headers. */
1882 if (subject_found == 0) {
1883 cprintf("Subject: (no subject)%s", nl);
1888 void Dump_RFC822HeadersBody(
1889 struct CtdlMessage *TheMessage,
1890 int headers_only, /* eschew the message body? */
1891 int flags, /* should the bessage be exported clean? */
1895 cit_uint8_t prev_ch;
1897 const char *StartOfText = StrBufNOTNULL;
1900 int nllen = strlen(nl);
1903 mptr = TheMessage->cm_fields[eMesageText];
1907 while (*mptr != '\0') {
1908 if (*mptr == '\r') {
1915 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1917 eoh = *(mptr+1) == '\n';
1921 StartOfText = strchr(StartOfText, '\n');
1922 StartOfText = strchr(StartOfText, '\n');
1925 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1926 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1927 ((headers_only != HEADERS_NONE) &&
1928 (headers_only != HEADERS_ONLY))
1930 if (*mptr == '\n') {
1931 memcpy(&outbuf[outlen], nl, nllen);
1933 outbuf[outlen] = '\0';
1936 outbuf[outlen++] = *mptr;
1940 if (flags & ESC_DOT)
1942 if ((prev_ch == '\n') &&
1944 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1946 outbuf[outlen++] = '.';
1951 if (outlen > 1000) {
1952 if (client_write(outbuf, outlen) == -1)
1954 struct CitContext *CCC = CC;
1955 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
1962 client_write(outbuf, outlen);
1968 /* If the format type on disk is 1 (fixed-format), then we want
1969 * everything to be output completely literally ... regardless of
1970 * what message transfer format is in use.
1972 void DumpFormatFixed(
1973 struct CtdlMessage *TheMessage,
1974 int mode, /* how would you like that message? */
1981 int nllen = strlen (nl);
1984 mptr = TheMessage->cm_fields[eMesageText];
1986 if (mode == MT_MIME) {
1987 cprintf("Content-type: text/plain\n\n");
1991 while (ch = *mptr++, ch > 0) {
1995 if ((buflen > 250) && (!xlline)){
1999 while ((buflen > 0) &&
2000 (!isspace(buf[buflen])))
2006 mptr -= tbuflen - buflen;
2011 /* if we reach the outer bounds of our buffer,
2012 abort without respect what whe purge. */
2015 (buflen > SIZ - nllen - 2)))
2019 memcpy (&buf[buflen], nl, nllen);
2023 if (client_write(buf, buflen) == -1)
2025 struct CitContext *CCC = CC;
2026 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2038 if (!IsEmptyStr(buf))
2039 cprintf("%s%s", buf, nl);
2043 * Get a message off disk. (returns om_* values found in msgbase.h)
2045 int CtdlOutputPreLoadedMsg(
2046 struct CtdlMessage *TheMessage,
2047 int mode, /* how would you like that message? */
2048 int headers_only, /* eschew the message body? */
2049 int do_proto, /* do Citadel protocol responses? */
2050 int crlf, /* Use CRLF newlines instead of LF? */
2051 int flags /* should the bessage be exported clean? */
2053 struct CitContext *CCC = CC;
2055 const char *nl; /* newline string */
2058 /* Buffers needed for RFC822 translation. These are all filled
2059 * using functions that are bounds-checked, and therefore we can
2060 * make them substantially smaller than SIZ.
2068 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2069 ((TheMessage == NULL) ? "NULL" : "not null"),
2070 mode, headers_only, do_proto, crlf);
2072 strcpy(mid, "unknown");
2073 nl = (crlf ? "\r\n" : "\n");
2075 if (!CM_IsValidMsg(TheMessage)) {
2076 MSGM_syslog(LOG_ERR,
2077 "ERROR: invalid preloaded message for output\n");
2079 return(om_no_such_msg);
2082 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2083 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2085 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2086 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2089 /* Are we downloading a MIME component? */
2090 if (mode == MT_DOWNLOAD) {
2091 if (TheMessage->cm_format_type != FMT_RFC822) {
2093 cprintf("%d This is not a MIME message.\n",
2094 ERROR + ILLEGAL_VALUE);
2095 } else if (CCC->download_fp != NULL) {
2096 if (do_proto) cprintf(
2097 "%d You already have a download open.\n",
2098 ERROR + RESOURCE_BUSY);
2100 /* Parse the message text component */
2101 mime_parser(CM_RANGE(TheMessage, eMesageText),
2102 *mime_download, NULL, NULL, NULL, 0);
2103 /* If there's no file open by this time, the requested
2104 * section wasn't found, so print an error
2106 if (CCC->download_fp == NULL) {
2107 if (do_proto) cprintf(
2108 "%d Section %s not found.\n",
2109 ERROR + FILE_NOT_FOUND,
2110 CCC->download_desired_section);
2113 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2116 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2117 * in a single server operation instead of opening a download file.
2119 if (mode == MT_SPEW_SECTION) {
2120 if (TheMessage->cm_format_type != FMT_RFC822) {
2122 cprintf("%d This is not a MIME message.\n",
2123 ERROR + ILLEGAL_VALUE);
2125 /* Parse the message text component */
2128 mime_parser(CM_RANGE(TheMessage, eMesageText),
2129 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2130 /* If section wasn't found, print an error
2133 if (do_proto) cprintf(
2134 "%d Section %s not found.\n",
2135 ERROR + FILE_NOT_FOUND,
2136 CCC->download_desired_section);
2139 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2142 /* now for the user-mode message reading loops */
2143 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2145 /* Does the caller want to skip the headers? */
2146 if (headers_only == HEADERS_NONE) goto START_TEXT;
2148 /* Tell the client which format type we're using. */
2149 if ( (mode == MT_CITADEL) && (do_proto) ) {
2150 cprintf("type=%d\n", TheMessage->cm_format_type);
2153 /* nhdr=yes means that we're only displaying headers, no body */
2154 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2155 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2158 cprintf("nhdr=yes\n");
2161 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2162 OutputCtdlMsgHeaders(TheMessage, do_proto);
2165 /* begin header processing loop for RFC822 transfer format */
2169 memcpy(snode, CFG_KEY(c_nodename) + 1);
2170 if (mode == MT_RFC822)
2171 OutputRFC822MsgHeaders(
2176 suser, sizeof(suser),
2177 luser, sizeof(luser),
2178 fuser, sizeof(fuser),
2179 snode, sizeof(snode)
2183 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2184 suser[i] = tolower(suser[i]);
2185 if (!isalnum(suser[i])) suser[i]='_';
2188 if (mode == MT_RFC822) {
2189 if (!strcasecmp(snode, NODENAME)) {
2190 safestrncpy(snode, FQDN, sizeof snode);
2193 /* Construct a fun message id */
2194 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2195 if (strchr(mid, '@')==NULL) {
2196 cprintf("@%s", snode);
2200 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2201 cprintf("From: \"----\" <x@x.org>%s", nl);
2203 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2204 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2206 else if (!IsEmptyStr(fuser)) {
2207 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2210 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2213 /* Blank line signifying RFC822 end-of-headers */
2214 if (TheMessage->cm_format_type != FMT_RFC822) {
2219 /* end header processing loop ... at this point, we're in the text */
2221 if (headers_only == HEADERS_FAST) goto DONE;
2223 /* Tell the client about the MIME parts in this message */
2224 if (TheMessage->cm_format_type == FMT_RFC822) {
2225 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2226 memset(&ma, 0, sizeof(struct ma_info));
2227 mime_parser(CM_RANGE(TheMessage, eMesageText),
2228 (do_proto ? *list_this_part : NULL),
2229 (do_proto ? *list_this_pref : NULL),
2230 (do_proto ? *list_this_suff : NULL),
2233 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2234 Dump_RFC822HeadersBody(
2243 if (headers_only == HEADERS_ONLY) {
2247 /* signify start of msg text */
2248 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2249 if (do_proto) cprintf("text\n");
2252 if (TheMessage->cm_format_type == FMT_FIXED)
2255 mode, /* how would you like that message? */
2258 /* If the message on disk is format 0 (Citadel vari-format), we
2259 * output using the formatter at 80 columns. This is the final output
2260 * form if the transfer format is RFC822, but if the transfer format
2261 * is Citadel proprietary, it'll still work, because the indentation
2262 * for new paragraphs is correct and the client will reformat the
2263 * message to the reader's screen width.
2265 if (TheMessage->cm_format_type == FMT_CITADEL) {
2266 if (mode == MT_MIME) {
2267 cprintf("Content-type: text/x-citadel-variformat\n\n");
2269 memfmout(TheMessage->cm_fields[eMesageText], nl);
2272 /* If the message on disk is format 4 (MIME), we've gotta hand it
2273 * off to the MIME parser. The client has already been told that
2274 * this message is format 1 (fixed format), so the callback function
2275 * we use will display those parts as-is.
2277 if (TheMessage->cm_format_type == FMT_RFC822) {
2278 memset(&ma, 0, sizeof(struct ma_info));
2280 if (mode == MT_MIME) {
2281 ma.use_fo_hooks = 0;
2282 strcpy(ma.chosen_part, "1");
2283 ma.chosen_pref = 9999;
2284 ma.dont_decode = CCC->msg4_dont_decode;
2285 mime_parser(CM_RANGE(TheMessage, eMesageText),
2286 *choose_preferred, *fixed_output_pre,
2287 *fixed_output_post, (void *)&ma, 1);
2288 mime_parser(CM_RANGE(TheMessage, eMesageText),
2289 *output_preferred, NULL, NULL, (void *)&ma, 1);
2292 ma.use_fo_hooks = 1;
2293 mime_parser(CM_RANGE(TheMessage, eMesageText),
2294 *fixed_output, *fixed_output_pre,
2295 *fixed_output_post, (void *)&ma, 0);
2300 DONE: /* now we're done */
2301 if (do_proto) cprintf("000\n");
2306 * Save one or more message pointers into a specified room
2307 * (Returns 0 for success, nonzero for failure)
2308 * roomname may be NULL to use the current room
2310 * Note that the 'supplied_msg' field may be set to NULL, in which case
2311 * the message will be fetched from disk, by number, if we need to perform
2312 * replication checks. This adds an additional database read, so if the
2313 * caller already has the message in memory then it should be supplied. (Obviously
2314 * this mode of operation only works if we're saving a single message.)
2316 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2317 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2319 struct CitContext *CCC = CC;
2321 char hold_rm[ROOMNAMELEN];
2322 struct cdbdata *cdbfr;
2325 long highest_msg = 0L;
2328 struct CtdlMessage *msg = NULL;
2330 long *msgs_to_be_merged = NULL;
2331 int num_msgs_to_be_merged = 0;
2333 MSG_syslog(LOG_DEBUG,
2334 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2335 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2338 strcpy(hold_rm, CCC->room.QRname);
2341 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2342 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2343 if (num_newmsgs > 1) supplied_msg = NULL;
2345 /* Now the regular stuff */
2346 if (CtdlGetRoomLock(&CCC->room,
2347 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2349 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2350 return(ERROR + ROOM_NOT_FOUND);
2354 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2355 num_msgs_to_be_merged = 0;
2358 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2359 if (cdbfr == NULL) {
2363 msglist = (long *) cdbfr->ptr;
2364 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2365 num_msgs = cdbfr->len / sizeof(long);
2370 /* Create a list of msgid's which were supplied by the caller, but do
2371 * not already exist in the target room. It is absolutely taboo to
2372 * have more than one reference to the same message in a room.
2374 for (i=0; i<num_newmsgs; ++i) {
2376 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2377 if (msglist[j] == newmsgidlist[i]) {
2382 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2386 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2389 * Now merge the new messages
2391 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2392 if (msglist == NULL) {
2393 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2394 free(msgs_to_be_merged);
2395 return (ERROR + INTERNAL_ERROR);
2397 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2398 num_msgs += num_msgs_to_be_merged;
2400 /* Sort the message list, so all the msgid's are in order */
2401 num_msgs = sort_msglist(msglist, num_msgs);
2403 /* Determine the highest message number */
2404 highest_msg = msglist[num_msgs - 1];
2406 /* Write it back to disk. */
2407 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2408 msglist, (int)(num_msgs * sizeof(long)));
2410 /* Free up the memory we used. */
2413 /* Update the highest-message pointer and unlock the room. */
2414 CCC->room.QRhighest = highest_msg;
2415 CtdlPutRoomLock(&CCC->room);
2417 /* Perform replication checks if necessary */
2418 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2419 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2421 for (i=0; i<num_msgs_to_be_merged; ++i) {
2422 msgid = msgs_to_be_merged[i];
2424 if (supplied_msg != NULL) {
2428 msg = CtdlFetchMessage(msgid, 0);
2432 ReplicationChecks(msg);
2434 /* If the message has an Exclusive ID, index that... */
2435 if (!CM_IsEmpty(msg, eExclusiveID)) {
2436 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
2439 /* Free up the memory we may have allocated */
2440 if (msg != supplied_msg) {
2449 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2452 /* Submit this room for processing by hooks */
2453 PerformRoomHooks(&CCC->room);
2455 /* Go back to the room we were in before we wandered here... */
2456 CtdlGetRoom(&CCC->room, hold_rm);
2458 /* Bump the reference count for all messages which were merged */
2459 if (!suppress_refcount_adj) {
2460 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2463 /* Free up memory... */
2464 if (msgs_to_be_merged != NULL) {
2465 free(msgs_to_be_merged);
2468 /* Return success. */
2474 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2477 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2478 int do_repl_check, struct CtdlMessage *supplied_msg)
2480 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2487 * Message base operation to save a new message to the message store
2488 * (returns new message number)
2490 * This is the back end for CtdlSubmitMsg() and should not be directly
2491 * called by server-side modules.
2494 long send_message(struct CtdlMessage *msg) {
2495 struct CitContext *CCC = CC;
2505 /* Get a new message number */
2506 newmsgid = get_new_message_number();
2507 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2508 (long unsigned int) time(NULL),
2509 (long unsigned int) newmsgid,
2513 /* Generate an ID if we don't have one already */
2514 if (CM_IsEmpty(msg, emessageId)) {
2515 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2518 /* If the message is big, set its body aside for storage elsewhere */
2519 if (!CM_IsEmpty(msg, eMesageText)) {
2520 if (msg->cm_lengths[eMesageText] > BIGMSG) {
2522 holdM = msg->cm_fields[eMesageText];
2523 msg->cm_fields[eMesageText] = NULL;
2524 holdMLen = msg->cm_lengths[eMesageText];
2525 msg->cm_lengths[eMesageText] = 0;
2529 /* Serialize our data structure for storage in the database */
2530 CtdlSerializeMessage(&smr, msg);
2533 msg->cm_fields[eMesageText] = holdM;
2534 msg->cm_lengths[eMesageText] = holdMLen;
2538 cprintf("%d Unable to serialize message\n",
2539 ERROR + INTERNAL_ERROR);
2543 /* Write our little bundle of joy into the message base */
2544 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2545 smr.ser, smr.len) < 0) {
2546 MSGM_syslog(LOG_ERR, "Can't store message\n");
2550 cdb_store(CDB_BIGMSGS,
2560 /* Free the memory we used for the serialized message */
2563 /* Return the *local* message ID to the caller
2564 * (even if we're storing an incoming network message)
2572 * Serialize a struct CtdlMessage into the format used on disk and network.
2574 * This function loads up a "struct ser_ret" (defined in server.h) which
2575 * contains the length of the serialized message and a pointer to the
2576 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2578 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2579 struct CtdlMessage *msg) /* unserialized msg */
2581 struct CitContext *CCC = CC;
2586 * Check for valid message format
2588 if (CM_IsValidMsg(msg) == 0) {
2589 MSGM_syslog(LOG_ERR, "CtdlSerializeMessage() aborting due to invalid message\n");
2596 for (i=0; i < NDiskFields; ++i)
2597 if (msg->cm_fields[FieldOrder[i]] != NULL)
2598 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2600 ret->ser = malloc(ret->len);
2601 if (ret->ser == NULL) {
2602 MSG_syslog(LOG_ERR, "CtdlSerializeMessage() malloc(%ld) failed: %s\n",
2603 (long)ret->len, strerror(errno));
2610 ret->ser[1] = msg->cm_anon_type;
2611 ret->ser[2] = msg->cm_format_type;
2614 for (i=0; i < NDiskFields; ++i)
2615 if (msg->cm_fields[FieldOrder[i]] != NULL)
2617 ret->ser[wlen++] = (char)FieldOrder[i];
2619 memcpy(&ret->ser[wlen],
2620 msg->cm_fields[FieldOrder[i]],
2621 msg->cm_lengths[FieldOrder[i]] + 1);
2623 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2626 if (ret->len != wlen) {
2627 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2628 (long)ret->len, (long)wlen);
2636 * Check to see if any messages already exist in the current room which
2637 * carry the same Exclusive ID as this one. If any are found, delete them.
2639 void ReplicationChecks(struct CtdlMessage *msg) {
2640 struct CitContext *CCC = CC;
2641 long old_msgnum = (-1L);
2643 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
2645 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2648 /* No exclusive id? Don't do anything. */
2649 if (msg == NULL) return;
2650 if (CM_IsEmpty(msg, eExclusiveID)) return;
2652 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2653 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
2655 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
2656 if (old_msgnum > 0L) {
2657 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2658 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
2665 * Save a message to disk and submit it into the delivery system.
2667 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2668 recptypes *recps, /* recipients (if mail) */
2669 const char *force, /* force a particular room? */
2670 int flags /* should the message be exported clean? */
2673 char hold_rm[ROOMNAMELEN];
2674 char actual_rm[ROOMNAMELEN];
2675 char force_room[ROOMNAMELEN];
2676 char content_type[SIZ]; /* We have to learn this */
2677 char recipient[SIZ];
2678 char bounce_to[1024];
2681 const char *mptr = NULL;
2682 struct ctdluser userbuf;
2684 struct MetaData smi;
2685 char *collected_addresses = NULL;
2686 struct addresses_to_be_filed *aptr = NULL;
2687 StrBuf *saved_rfc822_version = NULL;
2688 int qualified_for_journaling = 0;
2689 CitContext *CCC = MyContext();
2691 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2692 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2694 /* If this message has no timestamp, we take the liberty of
2695 * giving it one, right now.
2697 if (CM_IsEmpty(msg, eTimestamp)) {
2698 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2701 /* If this message has no path, we generate one.
2703 if (CM_IsEmpty(msg, eMessagePath)) {
2704 if (!CM_IsEmpty(msg, eAuthor)) {
2705 CM_CopyField(msg, eMessagePath, eAuthor);
2706 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2707 if (isspace(msg->cm_fields[eMessagePath][a])) {
2708 msg->cm_fields[eMessagePath][a] = ' ';
2713 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2717 if (force == NULL) {
2718 force_room[0] = '\0';
2721 strcpy(force_room, force);
2724 /* Learn about what's inside, because it's what's inside that counts */
2725 if (CM_IsEmpty(msg, eMesageText)) {
2726 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2730 switch (msg->cm_format_type) {
2732 strcpy(content_type, "text/x-citadel-variformat");
2735 strcpy(content_type, "text/plain");
2738 strcpy(content_type, "text/plain");
2739 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2742 safestrncpy(content_type, &mptr[13], sizeof content_type);
2743 striplt(content_type);
2744 aptr = content_type;
2745 while (!IsEmptyStr(aptr)) {
2757 /* Goto the correct room */
2758 room = (recps) ? CCC->room.QRname : SENTITEMS;
2759 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
2760 strcpy(hold_rm, CCC->room.QRname);
2761 strcpy(actual_rm, CCC->room.QRname);
2762 if (recps != NULL) {
2763 strcpy(actual_rm, SENTITEMS);
2766 /* If the user is a twit, move to the twit room for posting */
2768 if (CCC->user.axlevel == AxProbU) {
2769 strcpy(hold_rm, actual_rm);
2770 strcpy(actual_rm, config.c_twitroom);
2771 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
2775 /* ...or if this message is destined for Aide> then go there. */
2776 if (!IsEmptyStr(force_room)) {
2777 strcpy(actual_rm, force_room);
2780 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
2781 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2782 /* CtdlGetRoom(&CCC->room, actual_rm); */
2783 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2787 * If this message has no O (room) field, generate one.
2789 if (CM_IsEmpty(msg, eOriginalRoom)) {
2790 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
2793 /* Perform "before save" hooks (aborting if any return nonzero) */
2794 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
2795 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2798 * If this message has an Exclusive ID, and the room is replication
2799 * checking enabled, then do replication checks.
2801 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2802 ReplicationChecks(msg);
2805 /* Save it to disk */
2806 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
2807 newmsgid = send_message(msg);
2808 if (newmsgid <= 0L) return(-5);
2810 /* Write a supplemental message info record. This doesn't have to
2811 * be a critical section because nobody else knows about this message
2814 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
2815 memset(&smi, 0, sizeof(struct MetaData));
2816 smi.meta_msgnum = newmsgid;
2817 smi.meta_refcount = 0;
2818 safestrncpy(smi.meta_content_type, content_type,
2819 sizeof smi.meta_content_type);
2822 * Measure how big this message will be when rendered as RFC822.
2823 * We do this for two reasons:
2824 * 1. We need the RFC822 length for the new metadata record, so the
2825 * POP and IMAP services don't have to calculate message lengths
2826 * while the user is waiting (multiplied by potentially hundreds
2827 * or thousands of messages).
2828 * 2. If journaling is enabled, we will need an RFC822 version of the
2829 * message to attach to the journalized copy.
2831 if (CCC->redirect_buffer != NULL) {
2832 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2835 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2836 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2837 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
2838 saved_rfc822_version = CCC->redirect_buffer;
2839 CCC->redirect_buffer = NULL;
2843 /* Now figure out where to store the pointers */
2844 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
2846 /* If this is being done by the networker delivering a private
2847 * message, we want to BYPASS saving the sender's copy (because there
2848 * is no local sender; it would otherwise go to the Trashcan).
2850 if ((!CCC->internal_pgm) || (recps == NULL)) {
2851 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2852 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
2853 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2857 /* For internet mail, drop a copy in the outbound queue room */
2858 if ((recps != NULL) && (recps->num_internet > 0)) {
2859 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2862 /* If other rooms are specified, drop them there too. */
2863 if ((recps != NULL) && (recps->num_room > 0))
2864 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2865 extract_token(recipient, recps->recp_room, i,
2866 '|', sizeof recipient);
2867 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
2868 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2871 /* Bump this user's messages posted counter. */
2872 MSGM_syslog(LOG_DEBUG, "Updating user\n");
2873 CtdlLockGetCurrentUser();
2874 CCC->user.posted = CCC->user.posted + 1;
2875 CtdlPutCurrentUserLock();
2877 /* Decide where bounces need to be delivered */
2878 if ((recps != NULL) && (recps->bounce_to == NULL))
2881 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2882 CCC->user.fullname, config.c_nodename);
2884 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2885 msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
2886 recps->bounce_to = bounce_to;
2889 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2892 /* If this is private, local mail, make a copy in the
2893 * recipient's mailbox and bump the reference count.
2895 if ((recps != NULL) && (recps->num_local > 0))
2900 pch = recps->recp_local;
2901 recps->recp_local = recipient;
2902 ntokens = num_tokens(pch, '|');
2903 for (i=0; i<ntokens; ++i)
2905 extract_token(recipient, pch, i, '|', sizeof recipient);
2906 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n", recipient);
2907 if (CtdlGetUser(&userbuf, recipient) == 0) {
2908 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2909 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2910 CtdlBumpNewMailCounter(userbuf.usernum);
2911 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2914 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
2915 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2918 recps->recp_local = pch;
2921 /* Perform "after save" hooks */
2922 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
2924 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2925 CM_FlushField(msg, eVltMsgNum);
2927 /* Go back to the room we started from */
2928 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
2929 if (strcasecmp(hold_rm, CCC->room.QRname))
2930 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2933 * Any addresses to harvest for someone's address book?
2935 if ( (CCC->logged_in) && (recps != NULL) ) {
2936 collected_addresses = harvest_collected_addresses(msg);
2939 if (collected_addresses != NULL) {
2940 aptr = (struct addresses_to_be_filed *)
2941 malloc(sizeof(struct addresses_to_be_filed));
2942 CtdlMailboxName(actual_rm, sizeof actual_rm,
2943 &CCC->user, USERCONTACTSROOM);
2944 aptr->roomname = strdup(actual_rm);
2945 aptr->collected_addresses = collected_addresses;
2946 begin_critical_section(S_ATBF);
2949 end_critical_section(S_ATBF);
2953 * Determine whether this message qualifies for journaling.
2955 if (!CM_IsEmpty(msg, eJournal)) {
2956 qualified_for_journaling = 0;
2959 if (recps == NULL) {
2960 qualified_for_journaling = config.c_journal_pubmsgs;
2962 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
2963 qualified_for_journaling = config.c_journal_email;
2966 qualified_for_journaling = config.c_journal_pubmsgs;
2971 * Do we have to perform journaling? If so, hand off the saved
2972 * RFC822 version will be handed off to the journaler for background
2973 * submit. Otherwise, we have to free the memory ourselves.
2975 if (saved_rfc822_version != NULL) {
2976 if (qualified_for_journaling) {
2977 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
2980 FreeStrBuf(&saved_rfc822_version);
2984 if ((recps != NULL) && (recps->bounce_to == bounce_to))
2985 recps->bounce_to = NULL;
2993 * Convenience function for generating small administrative messages.
2995 void quickie_message(const char *from,
2996 const char *fromaddr,
3001 const char *subject)
3003 struct CtdlMessage *msg;
3004 recptypes *recp = NULL;
3006 msg = malloc(sizeof(struct CtdlMessage));
3007 memset(msg, 0, sizeof(struct CtdlMessage));
3008 msg->cm_magic = CTDLMESSAGE_MAGIC;
3009 msg->cm_anon_type = MES_NORMAL;
3010 msg->cm_format_type = format_type;
3013 CM_SetField(msg, eAuthor, from, strlen(from));
3015 else if (fromaddr != NULL) {
3017 CM_SetField(msg, eAuthor, fromaddr, strlen(fromaddr));
3018 pAt = strchr(msg->cm_fields[eAuthor], '@');
3020 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3024 msg->cm_fields[eAuthor] = strdup("Citadel");
3027 if (fromaddr != NULL) CM_SetField(msg, erFc822Addr, fromaddr, strlen(fromaddr));
3028 if (room != NULL) CM_SetField(msg, eOriginalRoom, room, strlen(room));
3029 CM_SetField(msg, eNodeName, CFG_KEY(c_nodename));
3031 CM_SetField(msg, eRecipient, to, strlen(to));
3032 recp = validate_recipients(to, NULL, 0);
3034 if (subject != NULL) {
3035 CM_SetField(msg, eMsgSubject, subject, strlen(subject));
3037 CM_SetField(msg, eMesageText, text, strlen(text));
3039 CtdlSubmitMsg(msg, recp, room, 0);
3041 if (recp != NULL) free_recipients(recp);
3044 void flood_protect_quickie_message(const char *from,
3045 const char *fromaddr,
3050 const char *subject,
3052 const char **CritStr,
3059 u_char rawdigest[MD5_DIGEST_LEN];
3060 struct MD5Context md5context;
3064 static const time_t tsday = (8*60*60); /* just care for a day... */
3067 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3068 MD5Init(&md5context);
3070 for (i = 0; i < nCriterions; i++)
3071 MD5Update(&md5context,
3072 (const unsigned char*)CritStr[i], CritStrLen[i]);
3073 MD5Update(&md5context,
3074 (const unsigned char*)timestamp, tslen);
3075 MD5Final(rawdigest, &md5context);
3077 guid = NewStrBufPlain(NULL,
3078 MD5_DIGEST_LEN * 2 + 12);
3079 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3080 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3081 if (StrLength(guid) > 40)
3082 StrBufCutAt(guid, 40, NULL);
3084 seenstamp = CheckIfAlreadySeen("FPAideMessage",
3091 if (seenstamp < tsday)
3094 /* yes, we did. flood protection kicks in. */
3096 "not sending message again - %ld < %ld \n", seenstamp, tsday);
3102 "sending message. %ld >= %ld", seenstamp, tsday);
3104 /* no, this message isn't sent recently; go ahead. */
3105 quickie_message(from,
3117 * Back end function used by CtdlMakeMessage() and similar functions
3119 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3121 size_t maxlen, /* maximum message length */
3122 StrBuf *exist, /* if non-null, append to it;
3123 exist is ALWAYS freed */
3124 int crlf, /* CRLF newlines instead of LF */
3125 int *sock /* socket handle or 0 for this session's client socket */
3134 LineBuf = NewStrBufPlain(NULL, SIZ);
3135 if (exist == NULL) {
3136 Message = NewStrBufPlain(NULL, 4 * SIZ);
3139 Message = NewStrBufDup(exist);
3142 /* Do we need to change leading ".." to "." for SMTP escaping? */
3143 if ((tlen == 1) && (*terminator == '.')) {
3147 /* read in the lines of message text one by one */
3150 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3155 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3157 if ((StrLength(LineBuf) == tlen) &&
3158 (!strcmp(ChrPtr(LineBuf), terminator)))
3161 if ( (!flushing) && (!finished) ) {
3163 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3166 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3169 /* Unescape SMTP-style input of two dots at the beginning of the line */
3171 (StrLength(LineBuf) == 2) &&
3172 (!strcmp(ChrPtr(LineBuf), "..")))
3174 StrBufCutLeft(LineBuf, 1);
3177 StrBufAppendBuf(Message, LineBuf, 0);
3180 /* if we've hit the max msg length, flush the rest */
3181 if (StrLength(Message) >= maxlen) flushing = 1;
3183 } while (!finished);
3184 FreeStrBuf(&LineBuf);
3188 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3192 FreeStrBuf(&(*Msg)->MsgBuf);
3198 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3200 size_t maxlen, /* maximum message length */
3201 size_t expectlen, /* if we expect a message, how long should it be? */
3202 StrBuf *exist, /* if non-null, append to it;
3203 exist is ALWAYS freed */
3204 long eLen, /* length of exist */
3205 int crlf /* CRLF newlines instead of LF */
3208 ReadAsyncMsg *NewMsg;
3210 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3211 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3213 if (exist == NULL) {
3216 if (expectlen == 0) {
3220 len = expectlen + 10;
3222 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3225 NewMsg->MsgBuf = NewStrBufDup(exist);
3227 /* Do we need to change leading ".." to "." for SMTP escaping? */
3228 if ((tlen == 1) && (*terminator == '.')) {
3232 NewMsg->terminator = terminator;
3233 NewMsg->tlen = tlen;
3235 NewMsg->maxlen = maxlen;
3237 NewMsg->crlf = crlf;
3243 * Back end function used by CtdlMakeMessage() and similar functions
3245 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3247 ReadAsyncMsg *ReadMsg;
3248 int MsgFinished = 0;
3249 eReadState Finished = eMustReadMore;
3254 const char *pch = ChrPtr(IO->SendBuf.Buf);
3255 const char *pchh = IO->SendBuf.ReadWritePointer;
3261 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3262 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3263 ((CitContext*)(IO->CitContext))->ServiceName,
3266 fd = fopen(fn, "a+");
3268 syslog(LOG_EMERG, "failed to open file %s: %s", fn, strerror(errno));
3274 ReadMsg = IO->ReadMsg;
3276 /* read in the lines of message text one by one */
3278 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3281 case eMustReadMore: /// read new from socket...
3283 if (IO->RecvBuf.ReadWritePointer != NULL) {
3284 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3285 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3287 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3291 fprintf(fd, "BufferEmpty! \n");
3297 case eBufferNotEmpty: /* shouldn't happen... */
3298 case eReadSuccess: /// done for now...
3300 case eReadFail: /// WHUT?
3306 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3307 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3310 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3313 else if (!ReadMsg->flushing) {
3316 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3319 /* Unescape SMTP-style input of two dots at the beginning of the line */
3320 if ((ReadMsg->dodot) &&
3321 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3322 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3325 fprintf(fd, "UnEscaped!\n");
3327 StrBufCutLeft(IO->IOBuf, 1);
3330 if (ReadMsg->crlf) {
3331 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3334 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3337 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3340 /* if we've hit the max msg length, flush the rest */
3341 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3343 } while (!MsgFinished);
3346 fprintf(fd, "Done with reading; %s.\n, ",
3347 (MsgFinished)?"Message Finished": "FAILED");
3351 return eReadSuccess;
3358 * Back end function used by CtdlMakeMessage() and similar functions
3360 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3362 size_t maxlen, /* maximum message length */
3363 StrBuf *exist, /* if non-null, append to it;
3364 exist is ALWAYS freed */
3365 int crlf, /* CRLF newlines instead of LF */
3366 int *sock /* socket handle or 0 for this session's client socket */
3371 Message = CtdlReadMessageBodyBuf(terminator,
3377 if (Message == NULL)
3380 return SmashStrBuf(&Message);
3383 struct CtdlMessage *CtdlMakeMessage(
3384 struct ctdluser *author, /* author's user structure */
3385 char *recipient, /* NULL if it's not mail */
3386 char *recp_cc, /* NULL if it's not mail */
3387 char *room, /* room where it's going */
3388 int type, /* see MES_ types in header file */
3389 int format_type, /* variformat, plain text, MIME... */
3390 char *fake_name, /* who we're masquerading as */
3391 char *my_email, /* which of my email addresses to use (empty is ok) */
3392 char *subject, /* Subject (optional) */
3393 char *supplied_euid, /* ...or NULL if this is irrelevant */
3394 char *preformatted_text, /* ...or NULL to read text from client */
3395 char *references /* Thread references */
3398 return CtdlMakeMessageLen(
3399 author, /* author's user structure */
3400 recipient, /* NULL if it's not mail */
3401 (recipient)?strlen(recipient) : 0,
3402 recp_cc, /* NULL if it's not mail */
3403 (recp_cc)?strlen(recp_cc): 0,
3404 room, /* room where it's going */
3405 (room)?strlen(room): 0,
3406 type, /* see MES_ types in header file */
3407 format_type, /* variformat, plain text, MIME... */
3408 fake_name, /* who we're masquerading as */
3409 (fake_name)?strlen(fake_name): 0,
3410 my_email, /* which of my email addresses to use (empty is ok) */
3411 (my_email)?strlen(my_email): 0,
3412 subject, /* Subject (optional) */
3413 (subject)?strlen(subject): 0,
3414 supplied_euid, /* ...or NULL if this is irrelevant */
3415 (supplied_euid)?strlen(supplied_euid):0,
3416 preformatted_text, /* ...or NULL to read text from client */
3417 (preformatted_text)?strlen(preformatted_text) : 0,
3418 references, /* Thread references */
3419 (references)?strlen(references):0);
3424 * Build a binary message to be saved on disk.
3425 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3426 * will become part of the message. This means you are no longer
3427 * responsible for managing that memory -- it will be freed along with
3428 * the rest of the fields when CM_Free() is called.)
3431 struct CtdlMessage *CtdlMakeMessageLen(
3432 struct ctdluser *author, /* author's user structure */
3433 char *recipient, /* NULL if it's not mail */
3435 char *recp_cc, /* NULL if it's not mail */
3437 char *room, /* room where it's going */
3439 int type, /* see MES_ types in header file */
3440 int format_type, /* variformat, plain text, MIME... */
3441 char *fake_name, /* who we're masquerading as */
3443 char *my_email, /* which of my email addresses to use (empty is ok) */
3445 char *subject, /* Subject (optional) */
3447 char *supplied_euid, /* ...or NULL if this is irrelevant */
3449 char *preformatted_text, /* ...or NULL to read text from client */
3451 char *references, /* Thread references */
3455 struct CitContext *CCC = CC;
3456 /* Don't confuse the poor folks if it's not routed mail. * /
3457 char dest_node[256] = "";*/
3460 struct CtdlMessage *msg;
3462 StrBuf *FakeEncAuthor = NULL;
3464 msg = malloc(sizeof(struct CtdlMessage));
3465 memset(msg, 0, sizeof(struct CtdlMessage));
3466 msg->cm_magic = CTDLMESSAGE_MAGIC;
3467 msg->cm_anon_type = type;
3468 msg->cm_format_type = format_type;
3470 if (recipient != NULL) rcplen = striplt(recipient);
3471 if (recp_cc != NULL) cclen = striplt(recp_cc);
3473 /* Path or Return-Path */
3475 CM_SetField(msg, eMessagePath, my_email, myelen);
3478 CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
3480 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3482 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3483 CM_SetField(msg, eTimestamp, buf, blen);
3486 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3489 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3491 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3492 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3493 FreeStrBuf(&FakeAuthor);
3495 if (CCC->room.QRflags & QR_MAILBOX) { /* room */
3496 CM_SetField(msg, eOriginalRoom, &CCC->room.QRname[11], strlen(&CCC->room.QRname[11]));
3499 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3502 CM_SetField(msg, eNodeName, CFG_KEY(c_nodename));
3503 CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode));
3506 CM_SetField(msg, eRecipient, recipient, rcplen);
3509 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3513 CM_SetField(msg, erFc822Addr, my_email, myelen);
3515 else if ( (author == &CCC->user) && (!IsEmptyStr(CCC->cs_inet_email)) ) {
3516 CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email));
3519 if (subject != NULL) {
3521 length = striplt(subject);
3527 while ((subject[i] != '\0') &&
3528 (IsAscii = isascii(subject[i]) != 0 ))
3531 CM_SetField(msg, eMsgSubject, subject, subjlen);
3532 else /* ok, we've got utf8 in the string. */
3535 rfc2047Subj = rfc2047encode(subject, length);
3536 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3543 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3547 CM_SetField(msg, eWeferences, references, reflen);
3550 if (preformatted_text != NULL) {
3551 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3555 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3556 if (MsgBody != NULL) {
3557 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3568 * API function to delete messages which match a set of criteria
3569 * (returns the actual number of messages deleted)
3571 int CtdlDeleteMessages(char *room_name, /* which room */
3572 long *dmsgnums, /* array of msg numbers to be deleted */
3573 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3574 char *content_type /* or "" for any. regular expressions expected. */
3577 struct CitContext *CCC = CC;
3578 struct ctdlroom qrbuf;
3579 struct cdbdata *cdbfr;
3580 long *msglist = NULL;
3581 long *dellist = NULL;
3584 int num_deleted = 0;
3586 struct MetaData smi;
3589 int need_to_free_re = 0;
3591 if (content_type) if (!IsEmptyStr(content_type)) {
3592 regcomp(&re, content_type, 0);
3593 need_to_free_re = 1;
3595 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
3596 room_name, num_dmsgnums, content_type);
3598 /* get room record, obtaining a lock... */
3599 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3600 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
3602 if (need_to_free_re) regfree(&re);
3603 return (0); /* room not found */
3605 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3607 if (cdbfr != NULL) {
3608 dellist = malloc(cdbfr->len);
3609 msglist = (long *) cdbfr->ptr;
3610 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3611 num_msgs = cdbfr->len / sizeof(long);
3615 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3616 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3617 int have_more_del = 1;
3619 num_msgs = sort_msglist(msglist, num_msgs);
3620 if (num_dmsgnums > 1)
3621 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3624 StrBuf *dbg = NewStrBuf();
3625 for (i = 0; i < num_dmsgnums; i++)
3626 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3627 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
3632 while ((i < num_msgs) && (have_more_del)) {
3635 /* Set/clear a bit for each criterion */
3637 /* 0 messages in the list or a null list means that we are
3638 * interested in deleting any messages which meet the other criteria.
3641 delete_this |= 0x01;
3644 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3649 if (msglist[i] == dmsgnums[j]) {
3650 delete_this |= 0x01;
3653 have_more_del = (j < num_dmsgnums);
3656 if (have_contenttype) {
3657 GetMetaData(&smi, msglist[i]);
3658 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3659 delete_this |= 0x02;
3662 delete_this |= 0x02;
3665 /* Delete message only if all bits are set */
3666 if (delete_this == 0x03) {
3667 dellist[num_deleted++] = msglist[i];
3674 StrBuf *dbg = NewStrBuf();
3675 for (i = 0; i < num_deleted; i++)
3676 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3677 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
3681 num_msgs = sort_msglist(msglist, num_msgs);
3682 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3683 msglist, (int)(num_msgs * sizeof(long)));
3686 qrbuf.QRhighest = msglist[num_msgs - 1];
3688 qrbuf.QRhighest = 0;
3690 CtdlPutRoomLock(&qrbuf);
3692 /* Go through the messages we pulled out of the index, and decrement
3693 * their reference counts by 1. If this is the only room the message
3694 * was in, the reference count will reach zero and the message will
3695 * automatically be deleted from the database. We do this in a
3696 * separate pass because there might be plug-in hooks getting called,
3697 * and we don't want that happening during an S_ROOMS critical
3701 for (i=0; i<num_deleted; ++i) {
3702 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3704 AdjRefCountList(dellist, num_deleted, -1);
3706 /* Now free the memory we used, and go away. */
3707 if (msglist != NULL) free(msglist);
3708 if (dellist != NULL) free(dellist);
3709 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
3710 if (need_to_free_re) regfree(&re);
3711 return (num_deleted);
3718 * GetMetaData() - Get the supplementary record for a message
3720 void GetMetaData(struct MetaData *smibuf, long msgnum)
3723 struct cdbdata *cdbsmi;
3726 memset(smibuf, 0, sizeof(struct MetaData));
3727 smibuf->meta_msgnum = msgnum;
3728 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3730 /* Use the negative of the message number for its supp record index */
3731 TheIndex = (0L - msgnum);
3733 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3734 if (cdbsmi == NULL) {
3735 return; /* record not found; go with defaults */
3737 memcpy(smibuf, cdbsmi->ptr,
3738 ((cdbsmi->len > sizeof(struct MetaData)) ?
3739 sizeof(struct MetaData) : cdbsmi->len));
3746 * PutMetaData() - (re)write supplementary record for a message
3748 void PutMetaData(struct MetaData *smibuf)
3752 /* Use the negative of the message number for the metadata db index */
3753 TheIndex = (0L - smibuf->meta_msgnum);
3755 cdb_store(CDB_MSGMAIN,
3756 &TheIndex, (int)sizeof(long),
3757 smibuf, (int)sizeof(struct MetaData));
3762 * AdjRefCount - submit an adjustment to the reference count for a message.
3763 * (These are just queued -- we actually process them later.)
3765 void AdjRefCount(long msgnum, int incr)
3767 struct CitContext *CCC = CC;
3768 struct arcq new_arcq;
3771 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
3773 begin_critical_section(S_SUPPMSGMAIN);
3774 if (arcfp == NULL) {
3775 arcfp = fopen(file_arcq, "ab+");
3776 chown(file_arcq, CTDLUID, (-1));
3777 chmod(file_arcq, 0600);
3779 end_critical_section(S_SUPPMSGMAIN);
3781 /* msgnum < 0 means that we're trying to close the file */
3783 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
3784 begin_critical_section(S_SUPPMSGMAIN);
3785 if (arcfp != NULL) {
3789 end_critical_section(S_SUPPMSGMAIN);
3794 * If we can't open the queue, perform the operation synchronously.
3796 if (arcfp == NULL) {
3797 TDAP_AdjRefCount(msgnum, incr);
3801 new_arcq.arcq_msgnum = msgnum;
3802 new_arcq.arcq_delta = incr;
3803 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3805 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3814 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3816 struct CitContext *CCC = CC;
3817 long i, the_size, offset;
3818 struct arcq *new_arcq;
3821 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
3823 begin_critical_section(S_SUPPMSGMAIN);
3824 if (arcfp == NULL) {
3825 arcfp = fopen(file_arcq, "ab+");
3826 chown(file_arcq, CTDLUID, (-1));
3827 chmod(file_arcq, 0600);
3829 end_critical_section(S_SUPPMSGMAIN);
3832 * If we can't open the queue, perform the operation synchronously.
3834 if (arcfp == NULL) {
3835 for (i = 0; i < nmsg; i++)
3836 TDAP_AdjRefCount(msgnum[i], incr);
3840 the_size = sizeof(struct arcq) * nmsg;
3841 new_arcq = malloc(the_size);
3842 for (i = 0; i < nmsg; i++) {
3843 new_arcq[i].arcq_msgnum = msgnum[i];
3844 new_arcq[i].arcq_delta = incr;
3848 while ((rv >= 0) && (offset < the_size))
3850 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
3852 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3868 * TDAP_ProcessAdjRefCountQueue()
3870 * Process the queue of message count adjustments that was created by calls
3871 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3872 * for each one. This should be an "off hours" operation.
3874 int TDAP_ProcessAdjRefCountQueue(void)
3876 struct CitContext *CCC = CC;
3877 char file_arcq_temp[PATH_MAX];
3880 struct arcq arcq_rec;
3881 int num_records_processed = 0;
3883 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
3885 begin_critical_section(S_SUPPMSGMAIN);
3886 if (arcfp != NULL) {
3891 r = link(file_arcq, file_arcq_temp);
3893 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3894 end_critical_section(S_SUPPMSGMAIN);
3895 return(num_records_processed);
3899 end_critical_section(S_SUPPMSGMAIN);
3901 fp = fopen(file_arcq_temp, "rb");
3903 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3904 return(num_records_processed);
3907 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3908 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3909 ++num_records_processed;
3913 r = unlink(file_arcq_temp);
3915 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3918 return(num_records_processed);
3924 * TDAP_AdjRefCount - adjust the reference count for a message.
3925 * This one does it "for real" because it's called by
3926 * the autopurger function that processes the queue
3927 * created by AdjRefCount(). If a message's reference
3928 * count becomes zero, we also delete the message from
3929 * disk and de-index it.
3931 void TDAP_AdjRefCount(long msgnum, int incr)
3933 struct CitContext *CCC = CC;
3935 struct MetaData smi;
3938 /* This is a *tight* critical section; please keep it that way, as
3939 * it may get called while nested in other critical sections.
3940 * Complicating this any further will surely cause deadlock!
3942 begin_critical_section(S_SUPPMSGMAIN);
3943 GetMetaData(&smi, msgnum);
3944 smi.meta_refcount += incr;
3946 end_critical_section(S_SUPPMSGMAIN);
3947 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
3948 msgnum, incr, smi.meta_refcount
3951 /* If the reference count is now zero, delete the message
3952 * (and its supplementary record as well).
3954 if (smi.meta_refcount == 0) {
3955 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
3957 /* Call delete hooks with NULL room to show it has gone altogether */
3958 PerformDeleteHooks(NULL, msgnum);
3960 /* Remove from message base */
3962 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3963 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3965 /* Remove metadata record */
3966 delnum = (0L - msgnum);
3967 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3973 * Write a generic object to this room
3975 * Note: this could be much more efficient. Right now we use two temporary
3976 * files, and still pull the message into memory as with all others.
3978 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3979 char *content_type, /* MIME type of this object */
3980 char *raw_message, /* Data to be written */
3981 off_t raw_length, /* Size of raw_message */
3982 struct ctdluser *is_mailbox, /* Mailbox room? */
3983 int is_binary, /* Is encoding necessary? */
3984 int is_unique, /* Del others of this type? */
3985 unsigned int flags /* Internal save flags */
3988 struct CitContext *CCC = CC;
3989 struct ctdlroom qrbuf;
3990 char roomname[ROOMNAMELEN];
3991 struct CtdlMessage *msg;
3992 StrBuf *encoded_message = NULL;
3994 if (is_mailbox != NULL) {
3995 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3998 safestrncpy(roomname, req_room, sizeof(roomname));
4001 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
4004 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
4007 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
4010 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
4011 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
4012 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
4015 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
4018 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
4022 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
4025 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
4028 MSGM_syslog(LOG_DEBUG, "Allocating\n");
4029 msg = malloc(sizeof(struct CtdlMessage));
4030 memset(msg, 0, sizeof(struct CtdlMessage));
4031 msg->cm_magic = CTDLMESSAGE_MAGIC;
4032 msg->cm_anon_type = MES_NORMAL;
4033 msg->cm_format_type = 4;
4034 CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
4035 CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
4036 CM_SetField(msg, eNodeName, CFG_KEY(c_nodename));
4037 CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode));
4038 msg->cm_flags = flags;
4040 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
4042 /* Create the requested room if we have to. */
4043 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4044 CtdlCreateRoom(roomname,
4045 ( (is_mailbox != NULL) ? 5 : 3 ),
4046 "", 0, 1, 0, VIEW_BBS);
4048 /* If the caller specified this object as unique, delete all
4049 * other objects of this type that are currently in the room.
4052 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
4053 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4056 /* Now write the data */
4057 CtdlSubmitMsg(msg, NULL, roomname, 0);
4063 /*****************************************************************************/
4064 /* MODULE INITIALIZATION STUFF */
4065 /*****************************************************************************/
4066 void SetMessageDebugEnabled(const int n)
4068 MessageDebugEnabled = n;
4070 CTDL_MODULE_INIT(msgbase)
4073 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
4076 /* return our Subversion id for the Log */