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