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+");
3269 ReadMsg = IO->ReadMsg;
3271 /* read in the lines of message text one by one */
3273 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3276 case eMustReadMore: /// read new from socket...
3278 if (IO->RecvBuf.ReadWritePointer != NULL) {
3279 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3280 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3282 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3286 fprintf(fd, "BufferEmpty! \n");
3292 case eBufferNotEmpty: /* shouldn't happen... */
3293 case eReadSuccess: /// done for now...
3295 case eReadFail: /// WHUT?
3301 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3302 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3305 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3308 else if (!ReadMsg->flushing) {
3311 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3314 /* Unescape SMTP-style input of two dots at the beginning of the line */
3315 if ((ReadMsg->dodot) &&
3316 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3317 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3320 fprintf(fd, "UnEscaped!\n");
3322 StrBufCutLeft(IO->IOBuf, 1);
3325 if (ReadMsg->crlf) {
3326 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3329 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3332 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3335 /* if we've hit the max msg length, flush the rest */
3336 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3338 } while (!MsgFinished);
3341 fprintf(fd, "Done with reading; %s.\n, ",
3342 (MsgFinished)?"Message Finished": "FAILED");
3346 return eReadSuccess;
3353 * Back end function used by CtdlMakeMessage() and similar functions
3355 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3357 size_t maxlen, /* maximum message length */
3358 StrBuf *exist, /* if non-null, append to it;
3359 exist is ALWAYS freed */
3360 int crlf, /* CRLF newlines instead of LF */
3361 int *sock /* socket handle or 0 for this session's client socket */
3366 Message = CtdlReadMessageBodyBuf(terminator,
3372 if (Message == NULL)
3375 return SmashStrBuf(&Message);
3378 struct CtdlMessage *CtdlMakeMessage(
3379 struct ctdluser *author, /* author's user structure */
3380 char *recipient, /* NULL if it's not mail */
3381 char *recp_cc, /* NULL if it's not mail */
3382 char *room, /* room where it's going */
3383 int type, /* see MES_ types in header file */
3384 int format_type, /* variformat, plain text, MIME... */
3385 char *fake_name, /* who we're masquerading as */
3386 char *my_email, /* which of my email addresses to use (empty is ok) */
3387 char *subject, /* Subject (optional) */
3388 char *supplied_euid, /* ...or NULL if this is irrelevant */
3389 char *preformatted_text, /* ...or NULL to read text from client */
3390 char *references /* Thread references */
3393 return CtdlMakeMessageLen(
3394 author, /* author's user structure */
3395 recipient, /* NULL if it's not mail */
3396 (recipient)?strlen(recipient) : 0,
3397 recp_cc, /* NULL if it's not mail */
3398 (recp_cc)?strlen(recp_cc): 0,
3399 room, /* room where it's going */
3400 (room)?strlen(room): 0,
3401 type, /* see MES_ types in header file */
3402 format_type, /* variformat, plain text, MIME... */
3403 fake_name, /* who we're masquerading as */
3404 (fake_name)?strlen(fake_name): 0,
3405 my_email, /* which of my email addresses to use (empty is ok) */
3406 (my_email)?strlen(my_email): 0,
3407 subject, /* Subject (optional) */
3408 (subject)?strlen(subject): 0,
3409 supplied_euid, /* ...or NULL if this is irrelevant */
3410 (supplied_euid)?strlen(supplied_euid):0,
3411 preformatted_text, /* ...or NULL to read text from client */
3412 (preformatted_text)?strlen(preformatted_text) : 0,
3413 references, /* Thread references */
3414 (references)?strlen(references):0);
3419 * Build a binary message to be saved on disk.
3420 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3421 * will become part of the message. This means you are no longer
3422 * responsible for managing that memory -- it will be freed along with
3423 * the rest of the fields when CM_Free() is called.)
3426 struct CtdlMessage *CtdlMakeMessageLen(
3427 struct ctdluser *author, /* author's user structure */
3428 char *recipient, /* NULL if it's not mail */
3430 char *recp_cc, /* NULL if it's not mail */
3432 char *room, /* room where it's going */
3434 int type, /* see MES_ types in header file */
3435 int format_type, /* variformat, plain text, MIME... */
3436 char *fake_name, /* who we're masquerading as */
3438 char *my_email, /* which of my email addresses to use (empty is ok) */
3440 char *subject, /* Subject (optional) */
3442 char *supplied_euid, /* ...or NULL if this is irrelevant */
3444 char *preformatted_text, /* ...or NULL to read text from client */
3446 char *references, /* Thread references */
3450 struct CitContext *CCC = CC;
3451 /* Don't confuse the poor folks if it's not routed mail. * /
3452 char dest_node[256] = "";*/
3455 struct CtdlMessage *msg;
3457 StrBuf *FakeEncAuthor = NULL;
3459 msg = malloc(sizeof(struct CtdlMessage));
3460 memset(msg, 0, sizeof(struct CtdlMessage));
3461 msg->cm_magic = CTDLMESSAGE_MAGIC;
3462 msg->cm_anon_type = type;
3463 msg->cm_format_type = format_type;
3465 if (recipient != NULL) rcplen = striplt(recipient);
3466 if (recp_cc != NULL) cclen = striplt(recp_cc);
3468 /* Path or Return-Path */
3470 CM_SetField(msg, eMessagePath, my_email, myelen);
3473 CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
3475 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3477 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3478 CM_SetField(msg, eTimestamp, buf, blen);
3481 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3484 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3486 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3487 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3488 FreeStrBuf(&FakeAuthor);
3490 if (CCC->room.QRflags & QR_MAILBOX) { /* room */
3491 CM_SetField(msg, eOriginalRoom, &CCC->room.QRname[11], strlen(&CCC->room.QRname[11]));
3494 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3497 CM_SetField(msg, eNodeName, CFG_KEY(c_nodename));
3498 CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode));
3501 CM_SetField(msg, eRecipient, recipient, rcplen);
3504 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3508 CM_SetField(msg, erFc822Addr, my_email, myelen);
3510 else if ( (author == &CCC->user) && (!IsEmptyStr(CCC->cs_inet_email)) ) {
3511 CM_SetField(msg, erFc822Addr, CCC->cs_inet_email, strlen(CCC->cs_inet_email));
3514 if (subject != NULL) {
3516 length = striplt(subject);
3522 while ((subject[i] != '\0') &&
3523 (IsAscii = isascii(subject[i]) != 0 ))
3526 CM_SetField(msg, eMsgSubject, subject, subjlen);
3527 else /* ok, we've got utf8 in the string. */
3530 rfc2047Subj = rfc2047encode(subject, length);
3531 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3538 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3542 CM_SetField(msg, eWeferences, references, reflen);
3545 if (preformatted_text != NULL) {
3546 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3550 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3551 if (MsgBody != NULL) {
3552 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3563 * API function to delete messages which match a set of criteria
3564 * (returns the actual number of messages deleted)
3566 int CtdlDeleteMessages(char *room_name, /* which room */
3567 long *dmsgnums, /* array of msg numbers to be deleted */
3568 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3569 char *content_type /* or "" for any. regular expressions expected. */
3572 struct CitContext *CCC = CC;
3573 struct ctdlroom qrbuf;
3574 struct cdbdata *cdbfr;
3575 long *msglist = NULL;
3576 long *dellist = NULL;
3579 int num_deleted = 0;
3581 struct MetaData smi;
3584 int need_to_free_re = 0;
3586 if (content_type) if (!IsEmptyStr(content_type)) {
3587 regcomp(&re, content_type, 0);
3588 need_to_free_re = 1;
3590 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
3591 room_name, num_dmsgnums, content_type);
3593 /* get room record, obtaining a lock... */
3594 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3595 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
3597 if (need_to_free_re) regfree(&re);
3598 return (0); /* room not found */
3600 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3602 if (cdbfr != NULL) {
3603 dellist = malloc(cdbfr->len);
3604 msglist = (long *) cdbfr->ptr;
3605 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3606 num_msgs = cdbfr->len / sizeof(long);
3610 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3611 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3612 int have_more_del = 1;
3614 num_msgs = sort_msglist(msglist, num_msgs);
3615 if (num_dmsgnums > 1)
3616 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3619 StrBuf *dbg = NewStrBuf();
3620 for (i = 0; i < num_dmsgnums; i++)
3621 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3622 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
3627 while ((i < num_msgs) && (have_more_del)) {
3630 /* Set/clear a bit for each criterion */
3632 /* 0 messages in the list or a null list means that we are
3633 * interested in deleting any messages which meet the other criteria.
3636 delete_this |= 0x01;
3639 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3644 if (msglist[i] == dmsgnums[j]) {
3645 delete_this |= 0x01;
3648 have_more_del = (j < num_dmsgnums);
3651 if (have_contenttype) {
3652 GetMetaData(&smi, msglist[i]);
3653 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3654 delete_this |= 0x02;
3657 delete_this |= 0x02;
3660 /* Delete message only if all bits are set */
3661 if (delete_this == 0x03) {
3662 dellist[num_deleted++] = msglist[i];
3669 StrBuf *dbg = NewStrBuf();
3670 for (i = 0; i < num_deleted; i++)
3671 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3672 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
3676 num_msgs = sort_msglist(msglist, num_msgs);
3677 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3678 msglist, (int)(num_msgs * sizeof(long)));
3681 qrbuf.QRhighest = msglist[num_msgs - 1];
3683 qrbuf.QRhighest = 0;
3685 CtdlPutRoomLock(&qrbuf);
3687 /* Go through the messages we pulled out of the index, and decrement
3688 * their reference counts by 1. If this is the only room the message
3689 * was in, the reference count will reach zero and the message will
3690 * automatically be deleted from the database. We do this in a
3691 * separate pass because there might be plug-in hooks getting called,
3692 * and we don't want that happening during an S_ROOMS critical
3696 for (i=0; i<num_deleted; ++i) {
3697 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3699 AdjRefCountList(dellist, num_deleted, -1);
3701 /* Now free the memory we used, and go away. */
3702 if (msglist != NULL) free(msglist);
3703 if (dellist != NULL) free(dellist);
3704 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
3705 if (need_to_free_re) regfree(&re);
3706 return (num_deleted);
3713 * GetMetaData() - Get the supplementary record for a message
3715 void GetMetaData(struct MetaData *smibuf, long msgnum)
3718 struct cdbdata *cdbsmi;
3721 memset(smibuf, 0, sizeof(struct MetaData));
3722 smibuf->meta_msgnum = msgnum;
3723 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3725 /* Use the negative of the message number for its supp record index */
3726 TheIndex = (0L - msgnum);
3728 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3729 if (cdbsmi == NULL) {
3730 return; /* record not found; go with defaults */
3732 memcpy(smibuf, cdbsmi->ptr,
3733 ((cdbsmi->len > sizeof(struct MetaData)) ?
3734 sizeof(struct MetaData) : cdbsmi->len));
3741 * PutMetaData() - (re)write supplementary record for a message
3743 void PutMetaData(struct MetaData *smibuf)
3747 /* Use the negative of the message number for the metadata db index */
3748 TheIndex = (0L - smibuf->meta_msgnum);
3750 cdb_store(CDB_MSGMAIN,
3751 &TheIndex, (int)sizeof(long),
3752 smibuf, (int)sizeof(struct MetaData));
3757 * AdjRefCount - submit an adjustment to the reference count for a message.
3758 * (These are just queued -- we actually process them later.)
3760 void AdjRefCount(long msgnum, int incr)
3762 struct CitContext *CCC = CC;
3763 struct arcq new_arcq;
3766 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
3768 begin_critical_section(S_SUPPMSGMAIN);
3769 if (arcfp == NULL) {
3770 arcfp = fopen(file_arcq, "ab+");
3771 chown(file_arcq, CTDLUID, (-1));
3772 chmod(file_arcq, 0600);
3774 end_critical_section(S_SUPPMSGMAIN);
3776 /* msgnum < 0 means that we're trying to close the file */
3778 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
3779 begin_critical_section(S_SUPPMSGMAIN);
3780 if (arcfp != NULL) {
3784 end_critical_section(S_SUPPMSGMAIN);
3789 * If we can't open the queue, perform the operation synchronously.
3791 if (arcfp == NULL) {
3792 TDAP_AdjRefCount(msgnum, incr);
3796 new_arcq.arcq_msgnum = msgnum;
3797 new_arcq.arcq_delta = incr;
3798 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3800 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3809 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3811 struct CitContext *CCC = CC;
3812 long i, the_size, offset;
3813 struct arcq *new_arcq;
3816 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
3818 begin_critical_section(S_SUPPMSGMAIN);
3819 if (arcfp == NULL) {
3820 arcfp = fopen(file_arcq, "ab+");
3821 chown(file_arcq, CTDLUID, (-1));
3822 chmod(file_arcq, 0600);
3824 end_critical_section(S_SUPPMSGMAIN);
3827 * If we can't open the queue, perform the operation synchronously.
3829 if (arcfp == NULL) {
3830 for (i = 0; i < nmsg; i++)
3831 TDAP_AdjRefCount(msgnum[i], incr);
3835 the_size = sizeof(struct arcq) * nmsg;
3836 new_arcq = malloc(the_size);
3837 for (i = 0; i < nmsg; i++) {
3838 new_arcq[i].arcq_msgnum = msgnum[i];
3839 new_arcq[i].arcq_delta = incr;
3843 while ((rv >= 0) && (offset < the_size))
3845 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
3847 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3863 * TDAP_ProcessAdjRefCountQueue()
3865 * Process the queue of message count adjustments that was created by calls
3866 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3867 * for each one. This should be an "off hours" operation.
3869 int TDAP_ProcessAdjRefCountQueue(void)
3871 struct CitContext *CCC = CC;
3872 char file_arcq_temp[PATH_MAX];
3875 struct arcq arcq_rec;
3876 int num_records_processed = 0;
3878 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
3880 begin_critical_section(S_SUPPMSGMAIN);
3881 if (arcfp != NULL) {
3886 r = link(file_arcq, file_arcq_temp);
3888 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3889 end_critical_section(S_SUPPMSGMAIN);
3890 return(num_records_processed);
3894 end_critical_section(S_SUPPMSGMAIN);
3896 fp = fopen(file_arcq_temp, "rb");
3898 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3899 return(num_records_processed);
3902 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3903 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3904 ++num_records_processed;
3908 r = unlink(file_arcq_temp);
3910 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3913 return(num_records_processed);
3919 * TDAP_AdjRefCount - adjust the reference count for a message.
3920 * This one does it "for real" because it's called by
3921 * the autopurger function that processes the queue
3922 * created by AdjRefCount(). If a message's reference
3923 * count becomes zero, we also delete the message from
3924 * disk and de-index it.
3926 void TDAP_AdjRefCount(long msgnum, int incr)
3928 struct CitContext *CCC = CC;
3930 struct MetaData smi;
3933 /* This is a *tight* critical section; please keep it that way, as
3934 * it may get called while nested in other critical sections.
3935 * Complicating this any further will surely cause deadlock!
3937 begin_critical_section(S_SUPPMSGMAIN);
3938 GetMetaData(&smi, msgnum);
3939 smi.meta_refcount += incr;
3941 end_critical_section(S_SUPPMSGMAIN);
3942 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
3943 msgnum, incr, smi.meta_refcount
3946 /* If the reference count is now zero, delete the message
3947 * (and its supplementary record as well).
3949 if (smi.meta_refcount == 0) {
3950 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
3952 /* Call delete hooks with NULL room to show it has gone altogether */
3953 PerformDeleteHooks(NULL, msgnum);
3955 /* Remove from message base */
3957 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3958 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
3960 /* Remove metadata record */
3961 delnum = (0L - msgnum);
3962 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
3968 * Write a generic object to this room
3970 * Note: this could be much more efficient. Right now we use two temporary
3971 * files, and still pull the message into memory as with all others.
3973 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
3974 char *content_type, /* MIME type of this object */
3975 char *raw_message, /* Data to be written */
3976 off_t raw_length, /* Size of raw_message */
3977 struct ctdluser *is_mailbox, /* Mailbox room? */
3978 int is_binary, /* Is encoding necessary? */
3979 int is_unique, /* Del others of this type? */
3980 unsigned int flags /* Internal save flags */
3983 struct CitContext *CCC = CC;
3984 struct ctdlroom qrbuf;
3985 char roomname[ROOMNAMELEN];
3986 struct CtdlMessage *msg;
3987 StrBuf *encoded_message = NULL;
3989 if (is_mailbox != NULL) {
3990 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
3993 safestrncpy(roomname, req_room, sizeof(roomname));
3996 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
3999 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
4002 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
4005 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
4006 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
4007 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
4010 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
4013 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
4017 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
4020 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
4023 MSGM_syslog(LOG_DEBUG, "Allocating\n");
4024 msg = malloc(sizeof(struct CtdlMessage));
4025 memset(msg, 0, sizeof(struct CtdlMessage));
4026 msg->cm_magic = CTDLMESSAGE_MAGIC;
4027 msg->cm_anon_type = MES_NORMAL;
4028 msg->cm_format_type = 4;
4029 CM_SetField(msg, eAuthor, CCC->user.fullname, strlen(CCC->user.fullname));
4030 CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
4031 CM_SetField(msg, eNodeName, CFG_KEY(c_nodename));
4032 CM_SetField(msg, eHumanNode, CFG_KEY(c_humannode));
4033 msg->cm_flags = flags;
4035 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
4037 /* Create the requested room if we have to. */
4038 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4039 CtdlCreateRoom(roomname,
4040 ( (is_mailbox != NULL) ? 5 : 3 ),
4041 "", 0, 1, 0, VIEW_BBS);
4043 /* If the caller specified this object as unique, delete all
4044 * other objects of this type that are currently in the room.
4047 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
4048 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4051 /* Now write the data */
4052 CtdlSubmitMsg(msg, NULL, roomname, 0);
4058 /*****************************************************************************/
4059 /* MODULE INITIALIZATION STUFF */
4060 /*****************************************************************************/
4061 void SetMessageDebugEnabled(const int n)
4063 MessageDebugEnabled = n;
4065 CTDL_MODULE_INIT(msgbase)
4068 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
4071 /* return our Subversion id for the Log */