2 * Implements the message store.
4 * Copyright (c) 1987-2017 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"
26 #include "clientsocket.h"
31 #include "internet_addressing.h"
32 #include "euidindex.h"
34 #include "journaling.h"
36 struct addresses_to_be_filed *atbf = NULL;
38 /* This temp file holds the queue of operations for AdjRefCount() */
39 static FILE *arcfp = NULL;
40 void AdjRefCountList(long *msgnum, long nmsg, int incr);
43 * These are the four-character field headers we use when outputting
44 * messages in Citadel format (as opposed to RFC822 format).
47 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
56 "from", /* A -> eAuthor */
57 NULL, /* B -> eBig_message */
58 NULL, /* C -> eRemoteRoom */
59 NULL, /* D -> eDestination */
60 "exti", /* E -> eXclusivID */
61 "rfca", /* F -> erFc822Addr */
63 "hnod", /* H -> eHumanNode */
64 "msgn", /* I -> emessageId */
65 "jrnl", /* J -> eJournal */
66 "rep2", /* K -> eReplyTo */
67 "list", /* L -> eListID */
68 "text", /* M -> eMesageText */
69 "node", /* N -> eNodeName */
70 "room", /* O -> eOriginalRoom */
71 "path", /* P -> eMessagePath */
73 "rcpt", /* R -> eRecipient */
74 "spec", /* S -> eSpecialField */
75 "time", /* T -> eTimestamp */
76 "subj", /* U -> eMsgSubject */
77 "nvto", /* V -> eenVelopeTo */
78 "wefw", /* W -> eWeferences */
80 "cccc", /* Y -> eCarbonCopY */
84 HashList *msgKeyLookup = NULL;
86 int GetFieldFromMnemonic(eMsgField *f, const char* c)
89 if (GetHash(msgKeyLookup, c, 4, &v)) {
96 void FillMsgKeyLookupTable(void)
100 msgKeyLookup = NewHash (1, FourHash);
102 for (i=0; i < 91; i++) {
103 if (msgkeys[i] != NULL) {
104 Put(msgKeyLookup, msgkeys[i], 4, (void*)i, reference_free_handler);
109 eMsgField FieldOrder[] = {
110 /* Important fields */
121 /* Semi-important fields */
127 /* G is not used yet, may become virus signature*/
130 /* Q is not used yet */
133 /* X is not used yet */
134 /* Z is not used yet */
141 /* Message text (MUST be last) */
143 /* Not saved to disk:
148 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
150 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
152 return !((Msg->cm_fields[which] != NULL) &&
153 (Msg->cm_fields[which][0] != '\0'));
156 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
158 if (Msg->cm_fields[which] != NULL)
159 free (Msg->cm_fields[which]);
160 Msg->cm_fields[which] = malloc(length + 1);
161 memcpy(Msg->cm_fields[which], buf, length);
162 Msg->cm_fields[which][length] = '\0';
163 Msg->cm_lengths[which] = length;
166 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
170 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
171 CM_SetField(Msg, which, buf, len);
173 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
175 if (Msg->cm_fields[WhichToCut] == NULL)
178 if (Msg->cm_lengths[WhichToCut] > maxlen)
180 Msg->cm_fields[WhichToCut][maxlen] = '\0';
181 Msg->cm_lengths[WhichToCut] = maxlen;
185 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
187 if (Msg->cm_fields[which] != NULL)
188 free (Msg->cm_fields[which]);
189 Msg->cm_fields[which] = NULL;
190 Msg->cm_lengths[which] = 0;
192 void CM_Flush(struct CtdlMessage *Msg)
196 if (CM_IsValidMsg(Msg) == 0)
199 for (i = 0; i < 256; ++i)
201 CM_FlushField(Msg, i);
205 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
208 if (Msg->cm_fields[WhichToPutTo] != NULL)
209 free (Msg->cm_fields[WhichToPutTo]);
211 if (Msg->cm_fields[WhichtToCopy] != NULL)
213 len = Msg->cm_lengths[WhichtToCopy];
214 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
215 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichtToCopy], len);
216 Msg->cm_fields[WhichToPutTo][len] = '\0';
217 Msg->cm_lengths[WhichToPutTo] = len;
221 Msg->cm_fields[WhichToPutTo] = NULL;
222 Msg->cm_lengths[WhichToPutTo] = 0;
227 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
229 if (Msg->cm_fields[which] != NULL) {
234 oldmsgsize = Msg->cm_lengths[which] + 1;
235 newmsgsize = length + oldmsgsize;
237 new = malloc(newmsgsize);
238 memcpy(new, buf, length);
239 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
240 free(Msg->cm_fields[which]);
241 Msg->cm_fields[which] = new;
242 Msg->cm_lengths[which] = newmsgsize - 1;
245 Msg->cm_fields[which] = malloc(length + 1);
246 memcpy(Msg->cm_fields[which], buf, length);
247 Msg->cm_fields[which][length] = '\0';
248 Msg->cm_lengths[which] = length;
252 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
254 if (Msg->cm_fields[which] != NULL)
255 free (Msg->cm_fields[which]);
257 Msg->cm_fields[which] = *buf;
259 Msg->cm_lengths[which] = length;
262 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
264 if (Msg->cm_fields[which] != NULL)
265 free (Msg->cm_fields[which]);
267 Msg->cm_lengths[which] = StrLength(*buf);
268 Msg->cm_fields[which] = SmashStrBuf(buf);
271 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
273 if (Msg->cm_fields[which] != NULL)
275 *retlen = Msg->cm_lengths[which];
276 *ret = Msg->cm_fields[which];
277 Msg->cm_fields[which] = NULL;
278 Msg->cm_lengths[which] = 0;
288 * Returns 1 if the supplied pointer points to a valid Citadel message.
289 * If the pointer is NULL or the magic number check fails, returns 0.
291 int CM_IsValidMsg(struct CtdlMessage *msg) {
294 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
295 syslog(LOG_WARNING, "CM_IsValidMsg() -- self-check failed\n");
301 void CM_FreeContents(struct CtdlMessage *msg)
305 for (i = 0; i < 256; ++i)
306 if (msg->cm_fields[i] != NULL) {
307 free(msg->cm_fields[i]);
308 msg->cm_lengths[i] = 0;
311 msg->cm_magic = 0; /* just in case */
314 * 'Destructor' for struct CtdlMessage
316 void CM_Free(struct CtdlMessage *msg)
318 if (CM_IsValidMsg(msg) == 0)
320 if (msg != NULL) free (msg);
323 CM_FreeContents(msg);
327 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
330 len = OrgMsg->cm_lengths[i];
331 NewMsg->cm_fields[i] = malloc(len + 1);
332 if (NewMsg->cm_fields[i] == NULL)
334 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
335 NewMsg->cm_fields[i][len] = '\0';
336 NewMsg->cm_lengths[i] = len;
340 struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
343 struct CtdlMessage *NewMsg;
345 if (CM_IsValidMsg(OrgMsg) == 0)
347 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
351 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
353 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
355 for (i = 0; i < 256; ++i)
357 if (OrgMsg->cm_fields[i] != NULL)
359 if (!CM_DupField(i, OrgMsg, NewMsg))
374 /* Determine if a given message matches the fields in a message template.
375 * Return 0 for a successful match.
377 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
380 /* If there aren't any fields in the template, all messages will
383 if (template == NULL) return(0);
385 /* Null messages are bogus. */
386 if (msg == NULL) return(1);
388 for (i='A'; i<='Z'; ++i) {
389 if (template->cm_fields[i] != NULL) {
390 if (msg->cm_fields[i] == NULL) {
391 /* Considered equal if temmplate is empty string */
392 if (IsEmptyStr(template->cm_fields[i])) continue;
395 if ((template->cm_lengths[i] != msg->cm_lengths[i]) ||
396 (strcasecmp(msg->cm_fields[i], template->cm_fields[i])))
401 /* All compares succeeded: we have a match! */
408 * Retrieve the "seen" message list for the current room.
410 void CtdlGetSeen(char *buf, int which_set) {
413 /* Learn about the user and room in question */
414 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
416 if (which_set == ctdlsetseen_seen)
417 safestrncpy(buf, vbuf.v_seen, SIZ);
418 if (which_set == ctdlsetseen_answered)
419 safestrncpy(buf, vbuf.v_answered, SIZ);
425 * Manipulate the "seen msgs" string (or other message set strings)
427 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
428 int target_setting, int which_set,
429 struct ctdluser *which_user, struct ctdlroom *which_room) {
430 struct cdbdata *cdbfr;
435 long hi = (-1L); /// TODO: we just write here. y?
444 char *is_set; /* actually an array of booleans */
446 /* Don't bother doing *anything* if we were passed a list of zero messages */
447 if (num_target_msgnums < 1) {
451 /* If no room was specified, we go with the current room. */
453 which_room = &CC->room;
456 /* If no user was specified, we go with the current user. */
458 which_user = &CC->user;
461 syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
462 num_target_msgnums, target_msgnums[0],
463 (target_setting ? "SET" : "CLEAR"),
467 /* Learn about the user and room in question */
468 CtdlGetRelationship(&vbuf, which_user, which_room);
470 /* Load the message list */
471 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
473 msglist = (long *) cdbfr->ptr;
474 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
475 num_msgs = cdbfr->len / sizeof(long);
478 return; /* No messages at all? No further action. */
481 is_set = malloc(num_msgs * sizeof(char));
482 memset(is_set, 0, (num_msgs * sizeof(char)) );
484 /* Decide which message set we're manipulating */
486 case ctdlsetseen_seen:
487 vset = NewStrBufPlain(vbuf.v_seen, -1);
489 case ctdlsetseen_answered:
490 vset = NewStrBufPlain(vbuf.v_answered, -1);
497 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
498 syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
499 for (i=0; i<num_msgs; ++i) {
500 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
502 syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
503 for (k=0; k<num_target_msgnums; ++k) {
504 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
508 syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
510 /* Translate the existing sequence set into an array of booleans */
511 setstr = NewStrBuf();
515 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
517 StrBufExtract_token(lostr, setstr, 0, ':');
518 if (StrBufNum_tokens(setstr, ':') >= 2) {
519 StrBufExtract_token(histr, setstr, 1, ':');
523 StrBufAppendBuf(histr, lostr, 0);
526 if (!strcmp(ChrPtr(histr), "*")) {
533 for (i = 0; i < num_msgs; ++i) {
534 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
544 /* Now translate the array of booleans back into a sequence set */
550 for (i=0; i<num_msgs; ++i) {
554 for (k=0; k<num_target_msgnums; ++k) {
555 if (msglist[i] == target_msgnums[k]) {
556 is_seen = target_setting;
560 if ((was_seen == 0) && (is_seen == 1)) {
563 else if ((was_seen == 1) && (is_seen == 0)) {
566 if (StrLength(vset) > 0) {
567 StrBufAppendBufPlain(vset, HKEY(","), 0);
570 StrBufAppendPrintf(vset, "%ld", hi);
573 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
577 if ((is_seen) && (i == num_msgs - 1)) {
578 if (StrLength(vset) > 0) {
579 StrBufAppendBufPlain(vset, HKEY(","), 0);
581 if ((i==0) || (was_seen == 0)) {
582 StrBufAppendPrintf(vset, "%ld", msglist[i]);
585 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
593 * We will have to stuff this string back into a 4096 byte buffer, so if it's
594 * larger than that now, truncate it by removing tokens from the beginning.
595 * The limit of 100 iterations is there to prevent an infinite loop in case
596 * something unexpected happens.
598 int number_of_truncations = 0;
599 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
600 StrBufRemove_token(vset, 0, ',');
601 ++number_of_truncations;
605 * If we're truncating the sequence set of messages marked with the 'seen' flag,
606 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
607 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
609 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
611 first_tok = NewStrBuf();
612 StrBufExtract_token(first_tok, vset, 0, ',');
613 StrBufRemove_token(vset, 0, ',');
615 if (StrBufNum_tokens(first_tok, ':') > 1) {
616 StrBufRemove_token(first_tok, 0, ':');
620 new_set = NewStrBuf();
621 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
622 StrBufAppendBuf(new_set, first_tok, 0);
623 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
624 StrBufAppendBuf(new_set, vset, 0);
627 FreeStrBuf(&first_tok);
631 syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
633 /* Decide which message set we're manipulating */
635 case ctdlsetseen_seen:
636 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
638 case ctdlsetseen_answered:
639 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
645 CtdlSetRelationship(&vbuf, which_user, which_room);
651 * API function to perform an operation for each qualifying message in the
652 * current room. (Returns the number of messages processed.)
654 int CtdlForEachMessage(int mode, long ref, char *search_string,
656 struct CtdlMessage *compare,
657 ForEachMsgCallback CallBack,
662 struct cdbdata *cdbfr;
663 long *msglist = NULL;
665 int num_processed = 0;
668 struct CtdlMessage *msg = NULL;
671 int printed_lastold = 0;
672 int num_search_msgs = 0;
673 long *search_msgs = NULL;
675 int need_to_free_re = 0;
678 if ((content_type) && (!IsEmptyStr(content_type))) {
679 regcomp(&re, content_type, 0);
683 /* Learn about the user and room in question */
684 if (server_shutting_down) {
685 if (need_to_free_re) regfree(&re);
688 CtdlGetUser(&CC->user, CC->curr_user);
690 if (server_shutting_down) {
691 if (need_to_free_re) regfree(&re);
694 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
696 if (server_shutting_down) {
697 if (need_to_free_re) regfree(&re);
701 /* Load the message list */
702 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
704 if (need_to_free_re) regfree(&re);
705 return 0; /* No messages at all? No further action. */
708 msglist = (long *) cdbfr->ptr;
709 num_msgs = cdbfr->len / sizeof(long);
711 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
712 cdb_free(cdbfr); /* we own this memory now */
715 * Now begin the traversal.
717 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
719 /* If the caller is looking for a specific MIME type, filter
720 * out all messages which are not of the type requested.
722 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
724 /* This call to GetMetaData() sits inside this loop
725 * so that we only do the extra database read per msg
726 * if we need to. Doing the extra read all the time
727 * really kills the server. If we ever need to use
728 * metadata for another search criterion, we need to
729 * move the read somewhere else -- but still be smart
730 * enough to only do the read if the caller has
731 * specified something that will need it.
733 if (server_shutting_down) {
734 if (need_to_free_re) regfree(&re);
738 GetMetaData(&smi, msglist[a]);
740 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
741 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
747 num_msgs = sort_msglist(msglist, num_msgs);
749 /* If a template was supplied, filter out the messages which
750 * don't match. (This could induce some delays!)
753 if (compare != NULL) {
754 for (a = 0; a < num_msgs; ++a) {
755 if (server_shutting_down) {
756 if (need_to_free_re) regfree(&re);
760 msg = CtdlFetchMessage(msglist[a], 1, 1);
762 if (CtdlMsgCmp(msg, compare)) {
771 /* If a search string was specified, get a message list from
772 * the full text index and remove messages which aren't on both
776 * Since the lists are sorted and strictly ascending, and the
777 * output list is guaranteed to be shorter than or equal to the
778 * input list, we overwrite the bottom of the input list. This
779 * eliminates the need to memmove big chunks of the list over and
782 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
784 /* Call search module via hook mechanism.
785 * NULL means use any search function available.
786 * otherwise replace with a char * to name of search routine
788 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
790 if (num_search_msgs > 0) {
794 orig_num_msgs = num_msgs;
796 for (i=0; i<orig_num_msgs; ++i) {
797 for (j=0; j<num_search_msgs; ++j) {
798 if (msglist[i] == search_msgs[j]) {
799 msglist[num_msgs++] = msglist[i];
805 num_msgs = 0; /* No messages qualify */
807 if (search_msgs != NULL) free(search_msgs);
809 /* Now that we've purged messages which don't contain the search
810 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
817 * Now iterate through the message list, according to the
818 * criteria supplied by the caller.
821 for (a = 0; a < num_msgs; ++a) {
822 if (server_shutting_down) {
823 if (need_to_free_re) regfree(&re);
825 return num_processed;
827 thismsg = msglist[a];
828 if (mode == MSGS_ALL) {
832 is_seen = is_msg_in_sequence_set(
833 vbuf.v_seen, thismsg);
834 if (is_seen) lastold = thismsg;
840 || ((mode == MSGS_OLD) && (is_seen))
841 || ((mode == MSGS_NEW) && (!is_seen))
842 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
843 || ((mode == MSGS_FIRST) && (a < ref))
844 || ((mode == MSGS_GT) && (thismsg > ref))
845 || ((mode == MSGS_LT) && (thismsg < ref))
846 || ((mode == MSGS_EQ) && (thismsg == ref))
849 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
851 CallBack(lastold, userdata);
855 if (CallBack) CallBack(thismsg, userdata);
859 if (need_to_free_re) regfree(&re);
862 * We cache the most recent msglist in order to do security checks later
864 if (CC->client_socket > 0) {
865 if (CC->cached_msglist != NULL) {
866 free(CC->cached_msglist);
868 CC->cached_msglist = msglist;
869 CC->cached_num_msgs = num_msgs;
875 return num_processed;
881 * memfmout() - Citadel text formatter and paginator.
882 * Although the original purpose of this routine was to format
883 * text to the reader's screen width, all we're really using it
884 * for here is to format text out to 80 columns before sending it
885 * to the client. The client software may reformat it again.
888 char *mptr, /* where are we going to get our text from? */
889 const char *nl /* string to terminate lines with */
892 unsigned char ch = 0;
899 while (ch=*(mptr++), ch != 0) {
902 if (client_write(outbuf, len) == -1)
904 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
908 if (client_write(nl, nllen) == -1)
910 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
915 else if (ch == '\r') {
916 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
918 else if (isspace(ch)) {
919 if (column > 72) { /* Beyond 72 columns, break on the next space */
920 if (client_write(outbuf, len) == -1)
922 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
926 if (client_write(nl, nllen) == -1)
928 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
941 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
942 if (client_write(outbuf, len) == -1)
944 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
948 if (client_write(nl, nllen) == -1)
950 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
958 if (client_write(outbuf, len) == -1)
960 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
963 client_write(nl, nllen);
971 * Callback function for mime parser that simply lists the part
973 void list_this_part(char *name, char *filename, char *partnum, char *disp,
974 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
975 char *cbid, void *cbuserdata)
979 ma = (struct ma_info *)cbuserdata;
980 if (ma->is_ma == 0) {
981 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
994 * Callback function for multipart prefix
996 void list_this_pref(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 (!strcasecmp(cbtype, "multipart/alternative")) {
1007 if (ma->is_ma == 0) {
1008 cprintf("pref=%s|%s\n", partnum, cbtype);
1013 * Callback function for multipart sufffix
1015 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1016 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1017 char *cbid, void *cbuserdata)
1021 ma = (struct ma_info *)cbuserdata;
1022 if (ma->is_ma == 0) {
1023 cprintf("suff=%s|%s\n", partnum, cbtype);
1025 if (!strcasecmp(cbtype, "multipart/alternative")) {
1032 * Callback function for mime parser that opens a section for downloading
1033 * we use serv_files function here:
1035 extern void OpenCmdResult(char *filename, const char *mime_type);
1036 void mime_download(char *name, char *filename, char *partnum, char *disp,
1037 void *content, char *cbtype, char *cbcharset, size_t length,
1038 char *encoding, char *cbid, void *cbuserdata)
1042 /* Silently go away if there's already a download open. */
1043 if (CC->download_fp != NULL)
1047 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1048 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1050 CC->download_fp = tmpfile();
1051 if (CC->download_fp == NULL) {
1052 syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1054 cprintf("%d cannot open temporary file: %s\n",
1055 ERROR + INTERNAL_ERROR, strerror(errno));
1059 rv = fwrite(content, length, 1, CC->download_fp);
1061 syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1063 cprintf("%d unable to write tempfile.\n",
1065 fclose(CC->download_fp);
1066 CC->download_fp = NULL;
1069 fflush(CC->download_fp);
1070 rewind(CC->download_fp);
1072 OpenCmdResult(filename, cbtype);
1079 * Callback function for mime parser that outputs a section all at once.
1080 * We can specify the desired section by part number *or* content-id.
1082 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1083 void *content, char *cbtype, char *cbcharset, size_t length,
1084 char *encoding, char *cbid, void *cbuserdata)
1086 int *found_it = (int *)cbuserdata;
1089 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1090 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1093 cprintf("%d %d|-1|%s|%s|%s\n",
1100 client_write(content, length);
1104 struct CtdlMessage *CtdlDeserializeMessage(long msgnum, int with_body, const char *Buffer, long Length)
1106 struct CtdlMessage *ret = NULL;
1108 const char *upper_bound;
1110 cit_uint8_t field_header;
1114 upper_bound = Buffer + Length;
1116 /* Parse the three bytes that begin EVERY message on disk.
1117 * The first is always 0xFF, the on-disk magic number.
1118 * The second is the anonymous/public type byte.
1119 * The third is the format type byte (vari, fixed, or MIME).
1123 syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1126 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1127 memset(ret, 0, sizeof(struct CtdlMessage));
1129 ret->cm_magic = CTDLMESSAGE_MAGIC;
1130 ret->cm_anon_type = *mptr++; /* Anon type byte */
1131 ret->cm_format_type = *mptr++; /* Format type byte */
1134 * The rest is zero or more arbitrary fields. Load them in.
1135 * We're done when we encounter either a zero-length field or
1136 * have just processed the 'M' (message text) field.
1139 field_header = '\0';
1142 /* work around possibly buggy messages: */
1143 while (field_header == '\0')
1145 if (mptr >= upper_bound) {
1148 field_header = *mptr++;
1150 if (mptr >= upper_bound) {
1153 which = field_header;
1156 CM_SetField(ret, which, mptr, len);
1158 mptr += len + 1; /* advance to next field */
1160 } while ((mptr < upper_bound) && (field_header != 'M'));
1167 * Load a message from disk into memory.
1168 * This is used by CtdlOutputMsg() and other fetch functions.
1170 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1171 * using the CM_Free(); function.
1173 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body, int run_msg_hooks)
1175 struct cdbdata *dmsgtext;
1176 struct CtdlMessage *ret = NULL;
1178 syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1179 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1180 if (dmsgtext == NULL) {
1181 syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1185 if (dmsgtext->ptr[dmsgtext->len - 1] != '\0')
1187 syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Forcefully terminating message!!\n", msgnum, with_body);
1188 dmsgtext->ptr[dmsgtext->len - 1] = '\0';
1191 ret = CtdlDeserializeMessage(msgnum, with_body, dmsgtext->ptr, dmsgtext->len);
1199 /* Always make sure there's something in the msg text field. If
1200 * it's NULL, the message text is most likely stored separately,
1201 * so go ahead and fetch that. Failing that, just set a dummy
1202 * body so other code doesn't barf.
1204 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1205 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1206 if (dmsgtext != NULL) {
1207 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len - 1);
1211 if (CM_IsEmpty(ret, eMesageText)) {
1212 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1215 /* Perform "before read" hooks (aborting if any return nonzero) */
1216 if (run_msg_hooks && (PerformMessageHooks(ret, NULL, EVT_BEFOREREAD) > 0)) {
1227 * Pre callback function for multipart/alternative
1229 * NOTE: this differs from the standard behavior for a reason. Normally when
1230 * displaying multipart/alternative you want to show the _last_ usable
1231 * format in the message. Here we show the _first_ one, because it's
1232 * usually text/plain. Since this set of functions is designed for text
1233 * output to non-MIME-aware clients, this is the desired behavior.
1236 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1237 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1238 char *cbid, void *cbuserdata)
1242 ma = (struct ma_info *)cbuserdata;
1243 syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1244 if (!strcasecmp(cbtype, "multipart/alternative")) {
1248 if (!strcasecmp(cbtype, "message/rfc822")) {
1254 * Post callback function for multipart/alternative
1256 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1257 void *content, char *cbtype, char *cbcharset, size_t length,
1258 char *encoding, char *cbid, void *cbuserdata)
1262 ma = (struct ma_info *)cbuserdata;
1263 syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1264 if (!strcasecmp(cbtype, "multipart/alternative")) {
1268 if (!strcasecmp(cbtype, "message/rfc822")) {
1274 * Inline callback function for mime parser that wants to display text
1276 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1277 void *content, char *cbtype, char *cbcharset, size_t length,
1278 char *encoding, char *cbid, void *cbuserdata)
1285 ma = (struct ma_info *)cbuserdata;
1288 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1289 partnum, filename, cbtype, (long)length);
1292 * If we're in the middle of a multipart/alternative scope and
1293 * we've already printed another section, skip this one.
1295 if ( (ma->is_ma) && (ma->did_print) ) {
1296 syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1301 if ( (!strcasecmp(cbtype, "text/plain"))
1302 || (IsEmptyStr(cbtype)) ) {
1305 client_write(wptr, length);
1306 if (wptr[length-1] != '\n') {
1313 if (!strcasecmp(cbtype, "text/html")) {
1314 ptr = html_to_ascii(content, length, 80, 0);
1316 client_write(ptr, wlen);
1317 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1324 if (ma->use_fo_hooks) {
1325 if (PerformFixedOutputHooks(cbtype, content, length)) {
1326 /* above function returns nonzero if it handled the part */
1331 if (strncasecmp(cbtype, "multipart/", 10)) {
1332 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1333 partnum, filename, cbtype, (long)length);
1339 * The client is elegant and sophisticated and wants to be choosy about
1340 * MIME content types, so figure out which multipart/alternative part
1341 * we're going to send.
1343 * We use a system of weights. When we find a part that matches one of the
1344 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1345 * and then set ma->chosen_pref to that MIME type's position in our preference
1346 * list. If we then hit another match, we only replace the first match if
1347 * the preference value is lower.
1349 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1350 void *content, char *cbtype, char *cbcharset, size_t length,
1351 char *encoding, char *cbid, void *cbuserdata)
1357 ma = (struct ma_info *)cbuserdata;
1359 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1360 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1361 // I don't know if there are any side effects! Please TEST TEST TEST
1362 //if (ma->is_ma > 0) {
1364 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1365 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1366 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1367 if (i < ma->chosen_pref) {
1368 syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1369 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1370 ma->chosen_pref = i;
1377 * Now that we've chosen our preferred part, output it.
1379 void output_preferred(char *name,
1393 int add_newline = 0;
1396 char *decoded = NULL;
1397 size_t bytes_decoded;
1400 ma = (struct ma_info *)cbuserdata;
1402 /* This is not the MIME part you're looking for... */
1403 if (strcasecmp(partnum, ma->chosen_part)) return;
1405 /* If the content-type of this part is in our preferred formats
1406 * list, we can simply output it verbatim.
1408 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1409 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1410 if (!strcasecmp(buf, cbtype)) {
1411 /* Yeah! Go! W00t!! */
1412 if (ma->dont_decode == 0)
1413 rc = mime_decode_now (content,
1419 break; /* Give us the chance, maybe theres another one. */
1421 if (rc == 0) text_content = (char *)content;
1423 text_content = decoded;
1424 length = bytes_decoded;
1427 if (text_content[length-1] != '\n') {
1430 cprintf("Content-type: %s", cbtype);
1431 if (!IsEmptyStr(cbcharset)) {
1432 cprintf("; charset=%s", cbcharset);
1434 cprintf("\nContent-length: %d\n",
1435 (int)(length + add_newline) );
1436 if (!IsEmptyStr(encoding)) {
1437 cprintf("Content-transfer-encoding: %s\n", encoding);
1440 cprintf("Content-transfer-encoding: 7bit\n");
1442 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1444 if (client_write(text_content, length) == -1)
1446 syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1449 if (add_newline) cprintf("\n");
1450 if (decoded != NULL) free(decoded);
1455 /* No translations required or possible: output as text/plain */
1456 cprintf("Content-type: text/plain\n\n");
1458 if (ma->dont_decode == 0)
1459 rc = mime_decode_now (content,
1465 return; /* Give us the chance, maybe theres another one. */
1467 if (rc == 0) text_content = (char *)content;
1469 text_content = decoded;
1470 length = bytes_decoded;
1473 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1474 length, encoding, cbid, cbuserdata);
1475 if (decoded != NULL) free(decoded);
1480 char desired_section[64];
1487 * Callback function for
1489 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1490 void *content, char *cbtype, char *cbcharset, size_t length,
1491 char *encoding, char *cbid, void *cbuserdata)
1493 struct encapmsg *encap;
1495 encap = (struct encapmsg *)cbuserdata;
1497 /* Only proceed if this is the desired section... */
1498 if (!strcasecmp(encap->desired_section, partnum)) {
1499 encap->msglen = length;
1500 encap->msg = malloc(length + 2);
1501 memcpy(encap->msg, content, length);
1508 * Determine whether the specified message exists in the cached_msglist
1509 * (This is a security check)
1511 int check_cached_msglist(long msgnum) {
1513 /* cases in which we skip the check */
1514 if (!CC) return om_ok; /* not a session */
1515 if (CC->client_socket <= 0) return om_ok; /* not a client session */
1516 if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1517 if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1520 /* Do a binary search within the cached_msglist for the requested msgnum */
1522 int max = (CC->cached_num_msgs - 1);
1524 while (max >= min) {
1525 int middle = min + (max-min) / 2 ;
1526 if (msgnum == CC->cached_msglist[middle]) {
1529 if (msgnum > CC->cached_msglist[middle]) {
1537 return om_access_denied;
1543 * Get a message off disk. (returns om_* values found in msgbase.h)
1546 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1547 int mode, /* how would you like that message? */
1548 int headers_only, /* eschew the message body? */
1549 int do_proto, /* do Citadel protocol responses? */
1550 int crlf, /* Use CRLF newlines instead of LF? */
1551 char *section, /* NULL or a message/rfc822 section */
1552 int flags, /* various flags; see msgbase.h */
1557 struct CtdlMessage *TheMessage = NULL;
1558 int retcode = CIT_OK;
1559 struct encapmsg encap;
1562 syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1564 (section ? section : "<>")
1567 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1570 if (r == om_not_logged_in) {
1571 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1574 cprintf("%d An unknown error has occurred.\n", ERROR);
1581 * Check to make sure the message is actually IN this room
1583 r = check_cached_msglist(msg_num);
1584 if (r == om_access_denied) {
1585 /* Not in the cache? We get ONE shot to check it again. */
1586 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1587 r = check_cached_msglist(msg_num);
1590 syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1591 msg_num, CC->room.QRname
1594 if (r == om_access_denied) {
1595 cprintf("%d message %ld was not found in this room\n",
1596 ERROR + HIGHER_ACCESS_REQUIRED,
1605 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1606 * request that we don't even bother loading the body into memory.
1608 if (headers_only == HEADERS_FAST) {
1609 TheMessage = CtdlFetchMessage(msg_num, 0, 1);
1612 TheMessage = CtdlFetchMessage(msg_num, 1, 1);
1615 if (TheMessage == NULL) {
1616 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1617 ERROR + MESSAGE_NOT_FOUND, msg_num);
1618 return(om_no_such_msg);
1621 /* Here is the weird form of this command, to process only an
1622 * encapsulated message/rfc822 section.
1624 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1625 memset(&encap, 0, sizeof encap);
1626 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1627 mime_parser(CM_RANGE(TheMessage, eMesageText),
1628 *extract_encapsulated_message,
1629 NULL, NULL, (void *)&encap, 0
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);
1642 if ((MessageID != NULL) && (*MessageID == NULL))
1645 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1647 CM_Free(TheMessage);
1651 encap.msg[encap.msglen] = 0;
1652 TheMessage = convert_internet_message(encap.msg);
1653 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1655 /* Now we let it fall through to the bottom of this
1656 * function, because TheMessage now contains the
1657 * encapsulated message instead of the top-level
1658 * message. Isn't that neat?
1663 cprintf("%d msg %ld has no part %s\n",
1664 ERROR + MESSAGE_NOT_FOUND,
1668 retcode = om_no_such_msg;
1673 /* Ok, output the message now */
1674 if (retcode == CIT_OK)
1675 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1676 if ((Author != NULL) && (*Author == NULL))
1679 CM_GetAsField(TheMessage, eAuthor, Author, &len);
1681 if ((Address != NULL) && (*Address == NULL))
1684 CM_GetAsField(TheMessage, erFc822Addr, Address, &len);
1686 if ((MessageID != NULL) && (*MessageID == NULL))
1689 CM_GetAsField(TheMessage, emessageId, MessageID, &len);
1692 CM_Free(TheMessage);
1699 void OutputCtdlMsgHeaders(
1700 struct CtdlMessage *TheMessage,
1701 int do_proto) /* do Citadel protocol responses? */
1706 char display_name[256];
1708 /* begin header processing loop for Citadel message format */
1709 safestrncpy(display_name, "<unknown>", sizeof display_name);
1710 if (!CM_IsEmpty(TheMessage, eAuthor)) {
1711 strcpy(buf, TheMessage->cm_fields[eAuthor]);
1712 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1713 safestrncpy(display_name, "****", sizeof display_name);
1715 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1716 safestrncpy(display_name, "anonymous", sizeof display_name);
1719 safestrncpy(display_name, buf, sizeof display_name);
1721 if ((is_room_aide())
1722 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1723 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1724 size_t tmp = strlen(display_name);
1725 snprintf(&display_name[tmp],
1726 sizeof display_name - tmp,
1731 /* Don't show Internet address for users on the
1732 * local Citadel network.
1735 if (!CM_IsEmpty(TheMessage, eNodeName) &&
1736 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
1741 /* Now spew the header fields in the order we like them. */
1742 for (i=0; i< NDiskFields; ++i) {
1744 Field = FieldOrder[i];
1745 if (Field != eMesageText) {
1746 if ( (!CM_IsEmpty(TheMessage, Field))
1747 && (msgkeys[Field] != NULL) ) {
1748 if ((Field == eenVelopeTo) ||
1749 (Field == eRecipient) ||
1750 (Field == eCarbonCopY)) {
1751 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
1753 if (Field == eAuthor) {
1754 if (do_proto) cprintf("%s=%s\n",
1758 else if ((Field == erFc822Addr) && (suppress_f)) {
1761 /* Masquerade display name if needed */
1763 if (do_proto) cprintf("%s=%s\n",
1765 TheMessage->cm_fields[Field]
1774 void OutputRFC822MsgHeaders(
1775 struct CtdlMessage *TheMessage,
1776 int flags, /* should the bessage be exported clean */
1777 const char *nl, int nlen,
1778 char *mid, long sizeof_mid,
1779 char *suser, long sizeof_suser,
1780 char *luser, long sizeof_luser,
1781 char *fuser, long sizeof_fuser,
1782 char *snode, long sizeof_snode)
1784 char datestamp[100];
1785 int subject_found = 0;
1792 for (i = 0; i < NDiskFields; ++i) {
1793 if (TheMessage->cm_fields[FieldOrder[i]]) {
1794 mptr = mpptr = TheMessage->cm_fields[FieldOrder[i]];
1795 switch (FieldOrder[i]) {
1797 safestrncpy(luser, mptr, sizeof_luser);
1798 safestrncpy(suser, mptr, sizeof_suser);
1801 if ((flags & QP_EADDR) != 0) {
1802 mptr = qp_encode_email_addrs(mptr);
1804 sanitize_truncated_recipient(mptr);
1805 cprintf("CC: %s%s", mptr, nl);
1808 cprintf("Return-Path: %s%s", mptr, nl);
1811 cprintf("List-ID: %s%s", mptr, nl);
1814 if ((flags & QP_EADDR) != 0)
1815 mptr = qp_encode_email_addrs(mptr);
1817 while ((*hptr != '\0') && isspace(*hptr))
1819 if (!IsEmptyStr(hptr))
1820 cprintf("Envelope-To: %s%s", hptr, nl);
1823 cprintf("Subject: %s%s", mptr, nl);
1827 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1830 safestrncpy(fuser, mptr, sizeof_fuser);
1831 /* case eOriginalRoom:
1832 cprintf("X-Citadel-Room: %s%s",
1837 safestrncpy(snode, mptr, sizeof_snode);
1840 if (haschar(mptr, '@') == 0)
1842 sanitize_truncated_recipient(mptr);
1843 cprintf("To: %s@%s", mptr, CtdlGetConfigStr("c_fqdn"));
1848 if ((flags & QP_EADDR) != 0) {
1849 mptr = qp_encode_email_addrs(mptr);
1851 sanitize_truncated_recipient(mptr);
1852 cprintf("To: %s", mptr);
1857 datestring(datestamp, sizeof datestamp,
1858 atol(mptr), DATESTRING_RFC822);
1859 cprintf("Date: %s%s", datestamp, nl);
1862 cprintf("References: ");
1863 k = num_tokens(mptr, '|');
1864 for (j=0; j<k; ++j) {
1865 extract_token(buf, mptr, j, '|', sizeof buf);
1866 cprintf("<%s>", buf);
1877 while ((*hptr != '\0') && isspace(*hptr))
1879 if (!IsEmptyStr(hptr))
1880 cprintf("Reply-To: %s%s", mptr, nl);
1896 /* these don't map to mime message headers. */
1904 if (subject_found == 0) {
1905 cprintf("Subject: (no subject)%s", nl);
1910 void Dump_RFC822HeadersBody(
1911 struct CtdlMessage *TheMessage,
1912 int headers_only, /* eschew the message body? */
1913 int flags, /* should the bessage be exported clean? */
1915 const char *nl, int nlen)
1917 cit_uint8_t prev_ch;
1919 const char *StartOfText = StrBufNOTNULL;
1922 int nllen = strlen(nl);
1926 mptr = TheMessage->cm_fields[eMesageText];
1930 while (*mptr != '\0') {
1931 if (*mptr == '\r') {
1938 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1940 eoh = *(mptr+1) == '\n';
1944 StartOfText = strchr(StartOfText, '\n');
1945 StartOfText = strchr(StartOfText, '\n');
1948 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1949 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1950 ((headers_only != HEADERS_NONE) &&
1951 (headers_only != HEADERS_ONLY))
1953 if (*mptr == '\n') {
1954 memcpy(&outbuf[outlen], nl, nllen);
1956 outbuf[outlen] = '\0';
1959 outbuf[outlen++] = *mptr;
1963 if (flags & ESC_DOT)
1965 if ((prev_ch == '\n') &&
1967 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1969 outbuf[outlen++] = '.';
1974 if (outlen > 1000) {
1975 if (client_write(outbuf, outlen) == -1)
1977 syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
1980 lfSent = (outbuf[outlen - 1] == '\n');
1985 client_write(outbuf, outlen);
1986 lfSent = (outbuf[outlen - 1] == '\n');
1989 client_write(nl, nlen);
1994 /* If the format type on disk is 1 (fixed-format), then we want
1995 * everything to be output completely literally ... regardless of
1996 * what message transfer format is in use.
1998 void DumpFormatFixed(
1999 struct CtdlMessage *TheMessage,
2000 int mode, /* how would you like that message? */
2001 const char *nl, int nllen)
2009 mptr = TheMessage->cm_fields[eMesageText];
2011 if (mode == MT_MIME) {
2012 cprintf("Content-type: text/plain\n\n");
2016 while (ch = *mptr++, ch > 0) {
2020 if ((buflen > 250) && (!xlline)){
2024 while ((buflen > 0) &&
2025 (!isspace(buf[buflen])))
2031 mptr -= tbuflen - buflen;
2036 /* if we reach the outer bounds of our buffer,
2037 abort without respect what whe purge. */
2040 (buflen > SIZ - nllen - 2)))
2044 memcpy (&buf[buflen], nl, nllen);
2048 if (client_write(buf, buflen) == -1)
2050 syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2062 if (!IsEmptyStr(buf))
2063 cprintf("%s%s", buf, nl);
2067 * Get a message off disk. (returns om_* values found in msgbase.h)
2069 int CtdlOutputPreLoadedMsg(
2070 struct CtdlMessage *TheMessage,
2071 int mode, /* how would you like that message? */
2072 int headers_only, /* eschew the message body? */
2073 int do_proto, /* do Citadel protocol responses? */
2074 int crlf, /* Use CRLF newlines instead of LF? */
2075 int flags /* should the bessage be exported clean? */
2078 const char *nl; /* newline string */
2082 /* Buffers needed for RFC822 translation. These are all filled
2083 * using functions that are bounds-checked, and therefore we can
2084 * make them substantially smaller than SIZ.
2092 syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2093 ((TheMessage == NULL) ? "NULL" : "not null"),
2094 mode, headers_only, do_proto, crlf);
2096 strcpy(mid, "unknown");
2097 nl = (crlf ? "\r\n" : "\n");
2098 nlen = crlf ? 2 : 1;
2100 if (!CM_IsValidMsg(TheMessage)) {
2102 "ERROR: invalid preloaded message for output\n");
2104 return(om_no_such_msg);
2107 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2108 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2110 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2111 memset(TheMessage->cm_fields[eenVelopeTo], ' ', TheMessage->cm_lengths[eenVelopeTo]);
2114 /* Are we downloading a MIME component? */
2115 if (mode == MT_DOWNLOAD) {
2116 if (TheMessage->cm_format_type != FMT_RFC822) {
2118 cprintf("%d This is not a MIME message.\n",
2119 ERROR + ILLEGAL_VALUE);
2120 } else if (CC->download_fp != NULL) {
2121 if (do_proto) cprintf(
2122 "%d You already have a download open.\n",
2123 ERROR + RESOURCE_BUSY);
2125 /* Parse the message text component */
2126 mime_parser(CM_RANGE(TheMessage, eMesageText),
2127 *mime_download, NULL, NULL, NULL, 0);
2128 /* If there's no file open by this time, the requested
2129 * section wasn't found, so print an error
2131 if (CC->download_fp == NULL) {
2132 if (do_proto) cprintf(
2133 "%d Section %s not found.\n",
2134 ERROR + FILE_NOT_FOUND,
2135 CC->download_desired_section);
2138 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2141 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2142 * in a single server operation instead of opening a download file.
2144 if (mode == MT_SPEW_SECTION) {
2145 if (TheMessage->cm_format_type != FMT_RFC822) {
2147 cprintf("%d This is not a MIME message.\n",
2148 ERROR + ILLEGAL_VALUE);
2150 /* Parse the message text component */
2153 mime_parser(CM_RANGE(TheMessage, eMesageText),
2154 *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2155 /* If section wasn't found, print an error
2158 if (do_proto) cprintf(
2159 "%d Section %s not found.\n",
2160 ERROR + FILE_NOT_FOUND,
2161 CC->download_desired_section);
2164 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2167 /* now for the user-mode message reading loops */
2168 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2170 /* Does the caller want to skip the headers? */
2171 if (headers_only == HEADERS_NONE) goto START_TEXT;
2173 /* Tell the client which format type we're using. */
2174 if ( (mode == MT_CITADEL) && (do_proto) ) {
2175 cprintf("type=%d\n", TheMessage->cm_format_type);
2178 /* nhdr=yes means that we're only displaying headers, no body */
2179 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2180 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2183 cprintf("nhdr=yes\n");
2186 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2187 OutputCtdlMsgHeaders(TheMessage, do_proto);
2190 /* begin header processing loop for RFC822 transfer format */
2194 memcpy(snode, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")) + 1);
2195 if (mode == MT_RFC822)
2196 OutputRFC822MsgHeaders(
2201 suser, sizeof(suser),
2202 luser, sizeof(luser),
2203 fuser, sizeof(fuser),
2204 snode, sizeof(snode)
2208 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2209 suser[i] = tolower(suser[i]);
2210 if (!isalnum(suser[i])) suser[i]='_';
2213 if (mode == MT_RFC822) {
2214 if (!strcasecmp(snode, NODENAME)) {
2215 safestrncpy(snode, FQDN, sizeof snode);
2218 /* Construct a fun message id */
2219 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2220 if (strchr(mid, '@')==NULL) {
2221 cprintf("@%s", snode);
2225 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2226 cprintf("From: \"----\" <x@x.org>%s", nl);
2228 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2229 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2231 else if (!IsEmptyStr(fuser)) {
2232 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2235 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2238 /* Blank line signifying RFC822 end-of-headers */
2239 if (TheMessage->cm_format_type != FMT_RFC822) {
2244 /* end header processing loop ... at this point, we're in the text */
2246 if (headers_only == HEADERS_FAST) goto DONE;
2248 /* Tell the client about the MIME parts in this message */
2249 if (TheMessage->cm_format_type == FMT_RFC822) {
2250 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2251 memset(&ma, 0, sizeof(struct ma_info));
2252 mime_parser(CM_RANGE(TheMessage, eMesageText),
2253 (do_proto ? *list_this_part : NULL),
2254 (do_proto ? *list_this_pref : NULL),
2255 (do_proto ? *list_this_suff : NULL),
2258 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2259 Dump_RFC822HeadersBody(
2268 if (headers_only == HEADERS_ONLY) {
2272 /* signify start of msg text */
2273 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2274 if (do_proto) cprintf("text\n");
2277 if (TheMessage->cm_format_type == FMT_FIXED)
2280 mode, /* how would you like that message? */
2283 /* If the message on disk is format 0 (Citadel vari-format), we
2284 * output using the formatter at 80 columns. This is the final output
2285 * form if the transfer format is RFC822, but if the transfer format
2286 * is Citadel proprietary, it'll still work, because the indentation
2287 * for new paragraphs is correct and the client will reformat the
2288 * message to the reader's screen width.
2290 if (TheMessage->cm_format_type == FMT_CITADEL) {
2291 if (mode == MT_MIME) {
2292 cprintf("Content-type: text/x-citadel-variformat\n\n");
2294 memfmout(TheMessage->cm_fields[eMesageText], nl);
2297 /* If the message on disk is format 4 (MIME), we've gotta hand it
2298 * off to the MIME parser. The client has already been told that
2299 * this message is format 1 (fixed format), so the callback function
2300 * we use will display those parts as-is.
2302 if (TheMessage->cm_format_type == FMT_RFC822) {
2303 memset(&ma, 0, sizeof(struct ma_info));
2305 if (mode == MT_MIME) {
2306 ma.use_fo_hooks = 0;
2307 strcpy(ma.chosen_part, "1");
2308 ma.chosen_pref = 9999;
2309 ma.dont_decode = CC->msg4_dont_decode;
2310 mime_parser(CM_RANGE(TheMessage, eMesageText),
2311 *choose_preferred, *fixed_output_pre,
2312 *fixed_output_post, (void *)&ma, 1);
2313 mime_parser(CM_RANGE(TheMessage, eMesageText),
2314 *output_preferred, NULL, NULL, (void *)&ma, 1);
2317 ma.use_fo_hooks = 1;
2318 mime_parser(CM_RANGE(TheMessage, eMesageText),
2319 *fixed_output, *fixed_output_pre,
2320 *fixed_output_post, (void *)&ma, 0);
2325 DONE: /* now we're done */
2326 if (do_proto) cprintf("000\n");
2331 * Save one or more message pointers into a specified room
2332 * (Returns 0 for success, nonzero for failure)
2333 * roomname may be NULL to use the current room
2335 * Note that the 'supplied_msg' field may be set to NULL, in which case
2336 * the message will be fetched from disk, by number, if we need to perform
2337 * replication checks. This adds an additional database read, so if the
2338 * caller already has the message in memory then it should be supplied. (Obviously
2339 * this mode of operation only works if we're saving a single message.)
2341 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2342 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2345 char hold_rm[ROOMNAMELEN];
2346 struct cdbdata *cdbfr;
2349 long highest_msg = 0L;
2352 struct CtdlMessage *msg = NULL;
2354 long *msgs_to_be_merged = NULL;
2355 int num_msgs_to_be_merged = 0;
2358 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2359 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2362 strcpy(hold_rm, CC->room.QRname);
2365 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2366 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2367 if (num_newmsgs > 1) supplied_msg = NULL;
2369 /* Now the regular stuff */
2370 if (CtdlGetRoomLock(&CC->room,
2371 ((roomname != NULL) ? roomname : CC->room.QRname) )
2373 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2374 return(ERROR + ROOM_NOT_FOUND);
2378 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2379 num_msgs_to_be_merged = 0;
2382 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2383 if (cdbfr == NULL) {
2387 msglist = (long *) cdbfr->ptr;
2388 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2389 num_msgs = cdbfr->len / sizeof(long);
2394 /* Create a list of msgid's which were supplied by the caller, but do
2395 * not already exist in the target room. It is absolutely taboo to
2396 * have more than one reference to the same message in a room.
2398 for (i=0; i<num_newmsgs; ++i) {
2400 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2401 if (msglist[j] == newmsgidlist[i]) {
2406 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2410 syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2413 * Now merge the new messages
2415 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2416 if (msglist == NULL) {
2417 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2418 free(msgs_to_be_merged);
2419 return (ERROR + INTERNAL_ERROR);
2421 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2422 num_msgs += num_msgs_to_be_merged;
2424 /* Sort the message list, so all the msgid's are in order */
2425 num_msgs = sort_msglist(msglist, num_msgs);
2427 /* Determine the highest message number */
2428 highest_msg = msglist[num_msgs - 1];
2430 /* Write it back to disk. */
2431 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2432 msglist, (int)(num_msgs * sizeof(long)));
2434 /* Free up the memory we used. */
2437 /* Update the highest-message pointer and unlock the room. */
2438 CC->room.QRhighest = highest_msg;
2439 CtdlPutRoomLock(&CC->room);
2441 /* Perform replication checks if necessary */
2442 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2443 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2445 for (i=0; i<num_msgs_to_be_merged; ++i) {
2446 msgid = msgs_to_be_merged[i];
2448 if (supplied_msg != NULL) {
2452 msg = CtdlFetchMessage(msgid, 0, 1);
2456 ReplicationChecks(msg);
2458 /* If the message has an Exclusive ID, index that... */
2459 if (!CM_IsEmpty(msg, eExclusiveID)) {
2460 index_message_by_euid(msg->cm_fields[eExclusiveID], &CC->room, msgid);
2463 /* Free up the memory we may have allocated */
2464 if (msg != supplied_msg) {
2473 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2476 /* Submit this room for processing by hooks */
2477 PerformRoomHooks(&CC->room);
2479 /* Go back to the room we were in before we wandered here... */
2480 CtdlGetRoom(&CC->room, hold_rm);
2482 /* Bump the reference count for all messages which were merged */
2483 if (!suppress_refcount_adj) {
2484 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2487 /* Free up memory... */
2488 if (msgs_to_be_merged != NULL) {
2489 free(msgs_to_be_merged);
2492 /* Return success. */
2498 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2501 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2502 int do_repl_check, struct CtdlMessage *supplied_msg)
2504 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2511 * Message base operation to save a new message to the message store
2512 * (returns new message number)
2514 * This is the back end for CtdlSubmitMsg() and should not be directly
2515 * called by server-side modules.
2518 long CtdlSaveThisMessage(struct CtdlMessage *msg, long msgid, int Reply) {
2526 * If the message is big, set its body aside for storage elsewhere
2527 * and we hide the message body from the serializer
2529 if (!CM_IsEmpty(msg, eMesageText) && msg->cm_lengths[eMesageText] > BIGMSG)
2532 holdM = msg->cm_fields[eMesageText];
2533 msg->cm_fields[eMesageText] = NULL;
2534 holdMLen = msg->cm_lengths[eMesageText];
2535 msg->cm_lengths[eMesageText] = 0;
2538 /* Serialize our data structure for storage in the database */
2539 CtdlSerializeMessage(&smr, msg);
2542 /* put the message body back into the message */
2543 msg->cm_fields[eMesageText] = holdM;
2544 msg->cm_lengths[eMesageText] = holdMLen;
2549 cprintf("%d Unable to serialize message\n",
2550 ERROR + INTERNAL_ERROR);
2553 syslog(LOG_ERR, "CtdlSaveMessage() unable to serialize message");
2559 /* Write our little bundle of joy into the message base */
2560 retval = cdb_store(CDB_MSGMAIN, &msgid, (int)sizeof(long),
2563 syslog(LOG_ERR, "Can't store message %ld: %ld", msgid, retval);
2567 retval = cdb_store(CDB_BIGMSGS,
2574 syslog(LOG_ERR, "failed to store message body for msgid %ld: %ld",
2580 /* Free the memory we used for the serialized message */
2586 long send_message(struct CtdlMessage *msg) {
2592 /* Get a new message number */
2593 newmsgid = get_new_message_number();
2595 /* Generate an ID if we don't have one already */
2596 if (CM_IsEmpty(msg, emessageId)) {
2597 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2598 (long unsigned int) time(NULL),
2599 (long unsigned int) newmsgid,
2600 CtdlGetConfigStr("c_fqdn")
2603 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
2606 retval = CtdlSaveThisMessage(msg, newmsgid, 1);
2612 /* Return the *local* message ID to the caller
2613 * (even if we're storing an incoming network message)
2621 * Serialize a struct CtdlMessage into the format used on disk and network.
2623 * This function loads up a "struct ser_ret" (defined in server.h) which
2624 * contains the length of the serialized message and a pointer to the
2625 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2627 void CtdlSerializeMessage(struct ser_ret *ret, /* return values */
2628 struct CtdlMessage *msg) /* unserialized msg */
2634 * Check for valid message format
2636 if (CM_IsValidMsg(msg) == 0) {
2637 syslog(LOG_ERR, "CtdlSerializeMessage() aborting due to invalid message\n");
2644 for (i=0; i < NDiskFields; ++i)
2645 if (msg->cm_fields[FieldOrder[i]] != NULL)
2646 ret->len += msg->cm_lengths[FieldOrder[i]] + 2;
2648 ret->ser = malloc(ret->len);
2649 if (ret->ser == NULL) {
2650 syslog(LOG_ERR, "CtdlSerializeMessage() malloc(%ld) failed: %s\n",
2651 (long)ret->len, strerror(errno));
2658 ret->ser[1] = msg->cm_anon_type;
2659 ret->ser[2] = msg->cm_format_type;
2662 for (i=0; i < NDiskFields; ++i)
2663 if (msg->cm_fields[FieldOrder[i]] != NULL)
2665 ret->ser[wlen++] = (char)FieldOrder[i];
2667 memcpy(&ret->ser[wlen],
2668 msg->cm_fields[FieldOrder[i]],
2669 msg->cm_lengths[FieldOrder[i]] + 1);
2671 wlen = wlen + msg->cm_lengths[FieldOrder[i]] + 1;
2674 if (ret->len != wlen) {
2675 syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2676 (long)ret->len, (long)wlen);
2684 * Check to see if any messages already exist in the current room which
2685 * carry the same Exclusive ID as this one. If any are found, delete them.
2687 void ReplicationChecks(struct CtdlMessage *msg) {
2688 long old_msgnum = (-1L);
2690 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2692 syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2695 /* No exclusive id? Don't do anything. */
2696 if (msg == NULL) return;
2697 if (CM_IsEmpty(msg, eExclusiveID)) return;
2699 /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2700 msg->cm_fields[eExclusiveID], CC->room.QRname);*/
2702 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CC->room);
2703 if (old_msgnum > 0L) {
2704 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2705 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2712 * Save a message to disk and submit it into the delivery system.
2714 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2715 recptypes *recps, /* recipients (if mail) */
2716 const char *force, /* force a particular room? */
2717 int flags /* should the message be exported clean? */
2720 char hold_rm[ROOMNAMELEN];
2721 char actual_rm[ROOMNAMELEN];
2722 char force_room[ROOMNAMELEN];
2723 char content_type[SIZ]; /* We have to learn this */
2724 char recipient[SIZ];
2725 char bounce_to[1024];
2728 const char *mptr = NULL;
2729 struct ctdluser userbuf;
2731 struct MetaData smi;
2732 char *collected_addresses = NULL;
2733 struct addresses_to_be_filed *aptr = NULL;
2734 StrBuf *saved_rfc822_version = NULL;
2735 int qualified_for_journaling = 0;
2737 syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2738 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
2740 /* If this message has no timestamp, we take the liberty of
2741 * giving it one, right now.
2743 if (CM_IsEmpty(msg, eTimestamp)) {
2744 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
2747 /* If this message has no path, we generate one.
2749 if (CM_IsEmpty(msg, eMessagePath)) {
2750 if (!CM_IsEmpty(msg, eAuthor)) {
2751 CM_CopyField(msg, eMessagePath, eAuthor);
2752 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
2753 if (isspace(msg->cm_fields[eMessagePath][a])) {
2754 msg->cm_fields[eMessagePath][a] = ' ';
2759 CM_SetField(msg, eMessagePath, HKEY("unknown"));
2763 if (force == NULL) {
2764 force_room[0] = '\0';
2767 strcpy(force_room, force);
2770 /* Learn about what's inside, because it's what's inside that counts */
2771 if (CM_IsEmpty(msg, eMesageText)) {
2772 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2776 switch (msg->cm_format_type) {
2778 strcpy(content_type, "text/x-citadel-variformat");
2781 strcpy(content_type, "text/plain");
2784 strcpy(content_type, "text/plain");
2785 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
2788 safestrncpy(content_type, &mptr[13], sizeof content_type);
2789 striplt(content_type);
2790 aptr = content_type;
2791 while (!IsEmptyStr(aptr)) {
2803 /* Goto the correct room */
2804 room = (recps) ? CC->room.QRname : SENTITEMS;
2805 syslog(LOG_DEBUG, "Selected room %s\n", room);
2806 strcpy(hold_rm, CC->room.QRname);
2807 strcpy(actual_rm, CC->room.QRname);
2808 if (recps != NULL) {
2809 strcpy(actual_rm, SENTITEMS);
2812 /* If the user is a twit, move to the twit room for posting */
2814 if (CC->user.axlevel == AxProbU) {
2815 strcpy(hold_rm, actual_rm);
2816 strcpy(actual_rm, CtdlGetConfigStr("c_twitroom"));
2817 syslog(LOG_DEBUG, "Diverting to twit room\n");
2821 /* ...or if this message is destined for Aide> then go there. */
2822 if (!IsEmptyStr(force_room)) {
2823 strcpy(actual_rm, force_room);
2826 syslog(LOG_DEBUG, "Final selection: %s (%s)\n", actual_rm, room);
2827 if (strcasecmp(actual_rm, CC->room.QRname)) {
2828 /* CtdlGetRoom(&CC->room, actual_rm); */
2829 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL, NULL, NULL);
2833 * If this message has no O (room) field, generate one.
2835 if (CM_IsEmpty(msg, eOriginalRoom) && !IsEmptyStr(CC->room.QRname)) {
2836 CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
2839 /* Perform "before save" hooks (aborting if any return nonzero) */
2840 syslog(LOG_DEBUG, "Performing before-save hooks\n");
2841 if (PerformMessageHooks(msg, recps, EVT_BEFORESAVE) > 0) return(-3);
2844 * If this message has an Exclusive ID, and the room is replication
2845 * checking enabled, then do replication checks.
2847 if (DoesThisRoomNeedEuidIndexing(&CC->room)) {
2848 ReplicationChecks(msg);
2851 /* Save it to disk */
2852 syslog(LOG_DEBUG, "Saving to disk\n");
2853 newmsgid = send_message(msg);
2854 if (newmsgid <= 0L) return(-5);
2856 /* Write a supplemental message info record. This doesn't have to
2857 * be a critical section because nobody else knows about this message
2860 syslog(LOG_DEBUG, "Creating MetaData record\n");
2861 memset(&smi, 0, sizeof(struct MetaData));
2862 smi.meta_msgnum = newmsgid;
2863 smi.meta_refcount = 0;
2864 safestrncpy(smi.meta_content_type, content_type,
2865 sizeof smi.meta_content_type);
2868 * Measure how big this message will be when rendered as RFC822.
2869 * We do this for two reasons:
2870 * 1. We need the RFC822 length for the new metadata record, so the
2871 * POP and IMAP services don't have to calculate message lengths
2872 * while the user is waiting (multiplied by potentially hundreds
2873 * or thousands of messages).
2874 * 2. If journaling is enabled, we will need an RFC822 version of the
2875 * message to attach to the journalized copy.
2877 if (CC->redirect_buffer != NULL) {
2878 syslog(LOG_ALERT, "CC->redirect_buffer is not NULL during message submission!\n");
2881 CC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
2882 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2883 smi.meta_rfc822_length = StrLength(CC->redirect_buffer);
2884 saved_rfc822_version = CC->redirect_buffer;
2885 CC->redirect_buffer = NULL;
2889 /* Now figure out where to store the pointers */
2890 syslog(LOG_DEBUG, "Storing pointers\n");
2892 /* If this is being done by the networker delivering a private
2893 * message, we want to BYPASS saving the sender's copy (because there
2894 * is no local sender; it would otherwise go to the Trashcan).
2896 if ((!CC->internal_pgm) || (recps == NULL)) {
2897 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2898 syslog(LOG_ERR, "ERROR saving message pointer!\n");
2899 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2903 /* For internet mail, drop a copy in the outbound queue room */
2904 if ((recps != NULL) && (recps->num_internet > 0)) {
2905 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2908 /* If other rooms are specified, drop them there too. */
2909 if ((recps != NULL) && (recps->num_room > 0))
2910 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2911 extract_token(recipient, recps->recp_room, i,
2912 '|', sizeof recipient);
2913 syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
2914 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2917 /* Bump this user's messages posted counter. */
2918 syslog(LOG_DEBUG, "Updating user\n");
2919 CtdlLockGetCurrentUser();
2920 CC->user.posted = CC->user.posted + 1;
2921 CtdlPutCurrentUserLock();
2923 /* Decide where bounces need to be delivered */
2924 if ((recps != NULL) && (recps->bounce_to == NULL))
2927 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2928 CC->user.fullname, CtdlGetConfigStr("c_nodename"));
2930 snprintf(bounce_to, sizeof bounce_to, "%s@%s",
2931 msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
2932 recps->bounce_to = bounce_to;
2935 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
2938 /* If this is private, local mail, make a copy in the
2939 * recipient's mailbox and bump the reference count.
2941 if ((recps != NULL) && (recps->num_local > 0))
2946 pch = recps->recp_local;
2947 recps->recp_local = recipient;
2948 ntokens = num_tokens(pch, '|');
2949 for (i=0; i<ntokens; ++i)
2951 extract_token(recipient, pch, i, '|', sizeof recipient);
2952 syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n", recipient);
2953 if (CtdlGetUser(&userbuf, recipient) == 0) {
2954 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2955 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2956 CtdlBumpNewMailCounter(userbuf.usernum);
2957 PerformMessageHooks(msg, recps, EVT_AFTERUSRMBOXSAVE);
2960 syslog(LOG_DEBUG, "No user <%s>\n", recipient);
2961 CtdlSaveMsgPointerInRoom(CtdlGetConfigStr("c_aideroom"), newmsgid, 0, msg);
2964 recps->recp_local = pch;
2967 /* Perform "after save" hooks */
2968 syslog(LOG_DEBUG, "Performing after-save hooks\n");
2970 PerformMessageHooks(msg, recps, EVT_AFTERSAVE);
2971 CM_FlushField(msg, eVltMsgNum);
2973 /* Go back to the room we started from */
2974 syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
2975 if (strcasecmp(hold_rm, CC->room.QRname))
2976 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL, NULL, NULL);
2979 * Any addresses to harvest for someone's address book?
2981 if ( (CC->logged_in) && (recps != NULL) ) {
2982 collected_addresses = harvest_collected_addresses(msg);
2985 if (collected_addresses != NULL) {
2986 aptr = (struct addresses_to_be_filed *)
2987 malloc(sizeof(struct addresses_to_be_filed));
2988 CtdlMailboxName(actual_rm, sizeof actual_rm,
2989 &CC->user, USERCONTACTSROOM);
2990 aptr->roomname = strdup(actual_rm);
2991 aptr->collected_addresses = collected_addresses;
2992 begin_critical_section(S_ATBF);
2995 end_critical_section(S_ATBF);
2999 * Determine whether this message qualifies for journaling.
3001 if (!CM_IsEmpty(msg, eJournal)) {
3002 qualified_for_journaling = 0;
3005 if (recps == NULL) {
3006 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
3008 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3009 qualified_for_journaling = CtdlGetConfigInt("c_journal_email");
3012 qualified_for_journaling = CtdlGetConfigInt("c_journal_pubmsgs");
3017 * Do we have to perform journaling? If so, hand off the saved
3018 * RFC822 version will be handed off to the journaler for background
3019 * submit. Otherwise, we have to free the memory ourselves.
3021 if (saved_rfc822_version != NULL) {
3022 if (qualified_for_journaling) {
3023 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3026 FreeStrBuf(&saved_rfc822_version);
3030 if ((recps != NULL) && (recps->bounce_to == bounce_to))
3031 recps->bounce_to = NULL;
3039 * Convenience function for generating small administrative messages.
3041 long quickie_message(const char *from,
3042 const char *fromaddr,
3047 const char *subject)
3049 struct CtdlMessage *msg;
3050 recptypes *recp = NULL;
3052 msg = malloc(sizeof(struct CtdlMessage));
3053 memset(msg, 0, sizeof(struct CtdlMessage));
3054 msg->cm_magic = CTDLMESSAGE_MAGIC;
3055 msg->cm_anon_type = MES_NORMAL;
3056 msg->cm_format_type = format_type;
3058 if (!IsEmptyStr(from)) {
3059 CM_SetField(msg, eAuthor, from, strlen(from));
3061 else if (!IsEmptyStr(fromaddr)) {
3063 CM_SetField(msg, eAuthor, fromaddr, strlen(fromaddr));
3064 pAt = strchr(msg->cm_fields[eAuthor], '@');
3066 CM_CutFieldAt(msg, eAuthor, pAt - msg->cm_fields[eAuthor]);
3070 msg->cm_fields[eAuthor] = strdup("Citadel");
3073 if (!IsEmptyStr(fromaddr)) CM_SetField(msg, erFc822Addr, fromaddr, strlen(fromaddr));
3074 if (!IsEmptyStr(room)) CM_SetField(msg, eOriginalRoom, room, strlen(room));
3075 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
3076 if (!IsEmptyStr(to)) {
3077 CM_SetField(msg, eRecipient, to, strlen(to));
3078 recp = validate_recipients(to, NULL, 0);
3080 if (!IsEmptyStr(subject)) {
3081 CM_SetField(msg, eMsgSubject, subject, strlen(subject));
3083 if (!IsEmptyStr(text)) {
3084 CM_SetField(msg, eMesageText, text, strlen(text));
3087 long msgnum = CtdlSubmitMsg(msg, recp, room, 0);
3089 if (recp != NULL) free_recipients(recp);
3094 void flood_protect_quickie_message(const char *from,
3095 const char *fromaddr,
3100 const char *subject,
3102 const char **CritStr,
3103 const long *CritStrLen,
3109 u_char rawdigest[MD5_DIGEST_LEN];
3110 struct MD5Context md5context;
3114 static const time_t tsday = (8*60*60); /* just care for a day... */
3117 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3118 MD5Init(&md5context);
3120 for (i = 0; i < nCriterions; i++)
3121 MD5Update(&md5context,
3122 (const unsigned char*)CritStr[i], CritStrLen[i]);
3123 MD5Update(&md5context,
3124 (const unsigned char*)timestamp, tslen);
3125 MD5Final(rawdigest, &md5context);
3127 guid = NewStrBufPlain(NULL,
3128 MD5_DIGEST_LEN * 2 + 12);
3129 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3130 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3131 if (StrLength(guid) > 40)
3132 StrBufCutAt(guid, 40, NULL);
3134 seenstamp = CheckIfAlreadySeen("FPAideMessage",
3141 if ((seenstamp > 0) && (seenstamp < tsday))
3144 /* yes, we did. flood protection kicks in. */
3146 "not sending message again - %ld < %ld \n", seenstamp, tsday);
3152 "sending message. %ld >= %ld", seenstamp, tsday);
3154 /* no, this message isn't sent recently; go ahead. */
3155 quickie_message(from,
3167 * Back end function used by CtdlMakeMessage() and similar functions
3169 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3171 size_t maxlen, /* maximum message length */
3172 StrBuf *exist, /* if non-null, append to it;
3173 exist is ALWAYS freed */
3174 int crlf, /* CRLF newlines instead of LF */
3175 int *sock /* socket handle or 0 for this session's client socket */
3184 LineBuf = NewStrBufPlain(NULL, SIZ);
3185 if (exist == NULL) {
3186 Message = NewStrBufPlain(NULL, 4 * SIZ);
3189 Message = NewStrBufDup(exist);
3192 /* Do we need to change leading ".." to "." for SMTP escaping? */
3193 if ((tlen == 1) && (*terminator == '.')) {
3197 /* read in the lines of message text one by one */
3200 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3205 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3207 if ((StrLength(LineBuf) == tlen) &&
3208 (!strcmp(ChrPtr(LineBuf), terminator)))
3211 if ( (!flushing) && (!finished) ) {
3213 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3216 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3219 /* Unescape SMTP-style input of two dots at the beginning of the line */
3221 (StrLength(LineBuf) == 2) &&
3222 (!strcmp(ChrPtr(LineBuf), "..")))
3224 StrBufCutLeft(LineBuf, 1);
3227 StrBufAppendBuf(Message, LineBuf, 0);
3230 /* if we've hit the max msg length, flush the rest */
3231 if (StrLength(Message) >= maxlen) flushing = 1;
3233 } while (!finished);
3234 FreeStrBuf(&LineBuf);
3238 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3242 FreeStrBuf(&(*Msg)->MsgBuf);
3248 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3250 size_t maxlen, /* maximum message length */
3251 size_t expectlen, /* if we expect a message, how long should it be? */
3252 StrBuf *exist, /* if non-null, append to it;
3253 exist is ALWAYS freed */
3254 long eLen, /* length of exist */
3255 int crlf /* CRLF newlines instead of LF */
3258 ReadAsyncMsg *NewMsg;
3260 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3261 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3263 if (exist == NULL) {
3266 if (expectlen == 0) {
3270 len = expectlen + 10;
3272 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3275 NewMsg->MsgBuf = NewStrBufDup(exist);
3277 /* Do we need to change leading ".." to "." for SMTP escaping? */
3278 if ((tlen == 1) && (*terminator == '.')) {
3282 NewMsg->terminator = terminator;
3283 NewMsg->tlen = tlen;
3285 NewMsg->maxlen = maxlen;
3287 NewMsg->crlf = crlf;
3293 * Back end function used by CtdlMakeMessage() and similar functions
3295 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3297 ReadAsyncMsg *ReadMsg;
3298 int MsgFinished = 0;
3299 eReadState Finished = eMustReadMore;
3304 const char *pch = ChrPtr(IO->SendBuf.Buf);
3305 const char *pchh = IO->SendBuf.ReadWritePointer;
3311 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3312 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3313 ((CitContext*)(IO->CitContext))->ServiceName,
3316 fd = fopen(fn, "a+");
3318 syslog(LOG_EMERG, "failed to open file %s: %s", fn, strerror(errno));
3324 ReadMsg = IO->ReadMsg;
3326 /* read in the lines of message text one by one */
3328 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3331 case eMustReadMore: /// read new from socket...
3333 if (IO->RecvBuf.ReadWritePointer != NULL) {
3334 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3335 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3337 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3341 fprintf(fd, "BufferEmpty! \n");
3347 case eBufferNotEmpty: /* shouldn't happen... */
3348 case eReadSuccess: /// done for now...
3350 case eReadFail: /// WHUT?
3356 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3357 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3360 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3363 else if (!ReadMsg->flushing) {
3366 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3369 /* Unescape SMTP-style input of two dots at the beginning of the line */
3370 if ((ReadMsg->dodot) &&
3371 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3372 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3375 fprintf(fd, "UnEscaped!\n");
3377 StrBufCutLeft(IO->IOBuf, 1);
3380 if (ReadMsg->crlf) {
3381 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3384 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3387 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3390 /* if we've hit the max msg length, flush the rest */
3391 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3393 } while (!MsgFinished);
3396 fprintf(fd, "Done with reading; %s.\n, ",
3397 (MsgFinished)?"Message Finished": "FAILED");
3401 return eReadSuccess;
3408 * Back end function used by CtdlMakeMessage() and similar functions
3410 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3412 size_t maxlen, /* maximum message length */
3413 StrBuf *exist, /* if non-null, append to it;
3414 exist is ALWAYS freed */
3415 int crlf, /* CRLF newlines instead of LF */
3416 int *sock /* socket handle or 0 for this session's client socket */
3421 Message = CtdlReadMessageBodyBuf(terminator,
3427 if (Message == NULL)
3430 return SmashStrBuf(&Message);
3433 struct CtdlMessage *CtdlMakeMessage(
3434 struct ctdluser *author, /* author's user structure */
3435 char *recipient, /* NULL if it's not mail */
3436 char *recp_cc, /* NULL if it's not mail */
3437 char *room, /* room where it's going */
3438 int type, /* see MES_ types in header file */
3439 int format_type, /* variformat, plain text, MIME... */
3440 char *fake_name, /* who we're masquerading as */
3441 char *my_email, /* which of my email addresses to use (empty is ok) */
3442 char *subject, /* Subject (optional) */
3443 char *supplied_euid, /* ...or NULL if this is irrelevant */
3444 char *preformatted_text, /* ...or NULL to read text from client */
3445 char *references /* Thread references */
3448 return CtdlMakeMessageLen(
3449 author, /* author's user structure */
3450 recipient, /* NULL if it's not mail */
3451 (recipient)?strlen(recipient) : 0,
3452 recp_cc, /* NULL if it's not mail */
3453 (recp_cc)?strlen(recp_cc): 0,
3454 room, /* room where it's going */
3455 (room)?strlen(room): 0,
3456 type, /* see MES_ types in header file */
3457 format_type, /* variformat, plain text, MIME... */
3458 fake_name, /* who we're masquerading as */
3459 (fake_name)?strlen(fake_name): 0,
3460 my_email, /* which of my email addresses to use (empty is ok) */
3461 (my_email)?strlen(my_email): 0,
3462 subject, /* Subject (optional) */
3463 (subject)?strlen(subject): 0,
3464 supplied_euid, /* ...or NULL if this is irrelevant */
3465 (supplied_euid)?strlen(supplied_euid):0,
3466 preformatted_text, /* ...or NULL to read text from client */
3467 (preformatted_text)?strlen(preformatted_text) : 0,
3468 references, /* Thread references */
3469 (references)?strlen(references):0);
3474 * Build a binary message to be saved on disk.
3475 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3476 * will become part of the message. This means you are no longer
3477 * responsible for managing that memory -- it will be freed along with
3478 * the rest of the fields when CM_Free() is called.)
3481 struct CtdlMessage *CtdlMakeMessageLen(
3482 struct ctdluser *author, /* author's user structure */
3483 char *recipient, /* NULL if it's not mail */
3485 char *recp_cc, /* NULL if it's not mail */
3487 char *room, /* room where it's going */
3489 int type, /* see MES_ types in header file */
3490 int format_type, /* variformat, plain text, MIME... */
3491 char *fake_name, /* who we're masquerading as */
3493 char *my_email, /* which of my email addresses to use (empty is ok) */
3495 char *subject, /* Subject (optional) */
3497 char *supplied_euid, /* ...or NULL if this is irrelevant */
3499 char *preformatted_text, /* ...or NULL to read text from client */
3501 char *references, /* Thread references */
3505 /* Don't confuse the poor folks if it's not routed mail. * /
3506 char dest_node[256] = "";*/
3509 struct CtdlMessage *msg;
3511 StrBuf *FakeEncAuthor = NULL;
3513 msg = malloc(sizeof(struct CtdlMessage));
3514 memset(msg, 0, sizeof(struct CtdlMessage));
3515 msg->cm_magic = CTDLMESSAGE_MAGIC;
3516 msg->cm_anon_type = type;
3517 msg->cm_format_type = format_type;
3519 if (recipient != NULL) rcplen = striplt(recipient);
3520 if (recp_cc != NULL) cclen = striplt(recp_cc);
3522 /* Path or Return-Path */
3524 CM_SetField(msg, eMessagePath, my_email, myelen);
3526 else if (!IsEmptyStr(author->fullname)) {
3527 CM_SetField(msg, eMessagePath, author->fullname, strlen(author->fullname));
3529 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
3531 blen = snprintf(buf, sizeof buf, "%ld", (long)time(NULL));
3532 CM_SetField(msg, eTimestamp, buf, blen);
3535 FakeAuthor = NewStrBufPlain (fake_name, fnlen);
3538 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3540 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3541 CM_SetAsFieldSB(msg, eAuthor, &FakeEncAuthor);
3542 FreeStrBuf(&FakeAuthor);
3544 if (!!IsEmptyStr(CC->room.QRname)) {
3545 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3546 CM_SetField(msg, eOriginalRoom, &CC->room.QRname[11], strlen(&CC->room.QRname[11]));
3549 CM_SetField(msg, eOriginalRoom, CC->room.QRname, strlen(CC->room.QRname));
3553 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
3554 CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
3557 CM_SetField(msg, eRecipient, recipient, rcplen);
3560 CM_SetField(msg, eCarbonCopY, recp_cc, cclen);
3564 CM_SetField(msg, erFc822Addr, my_email, myelen);
3566 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3567 CM_SetField(msg, erFc822Addr, CC->cs_inet_email, strlen(CC->cs_inet_email));
3570 if (subject != NULL) {
3572 length = striplt(subject);
3578 while ((subject[i] != '\0') &&
3579 (IsAscii = isascii(subject[i]) != 0 ))
3582 CM_SetField(msg, eMsgSubject, subject, subjlen);
3583 else /* ok, we've got utf8 in the string. */
3586 rfc2047Subj = rfc2047encode(subject, length);
3587 CM_SetAsField(msg, eMsgSubject, &rfc2047Subj, strlen(rfc2047Subj));
3594 CM_SetField(msg, eExclusiveID, supplied_euid, euidlen);
3598 CM_SetField(msg, eWeferences, references, reflen);
3601 if (preformatted_text != NULL) {
3602 CM_SetField(msg, eMesageText, preformatted_text, textlen);
3606 MsgBody = CtdlReadMessageBodyBuf(HKEY("000"), CtdlGetConfigLong("c_maxmsglen"), NULL, 0, 0);
3607 if (MsgBody != NULL) {
3608 CM_SetAsFieldSB(msg, eMesageText, &MsgBody);
3619 * API function to delete messages which match a set of criteria
3620 * (returns the actual number of messages deleted)
3622 int CtdlDeleteMessages(const char *room_name, /* which room */
3623 long *dmsgnums, /* array of msg numbers to be deleted */
3624 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
3625 char *content_type /* or "" for any. regular expressions expected. */
3628 struct ctdlroom qrbuf;
3629 struct cdbdata *cdbfr;
3630 long *msglist = NULL;
3631 long *dellist = NULL;
3634 int num_deleted = 0;
3636 struct MetaData smi;
3639 int need_to_free_re = 0;
3641 if (content_type) if (!IsEmptyStr(content_type)) {
3642 regcomp(&re, content_type, 0);
3643 need_to_free_re = 1;
3645 syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
3646 room_name, num_dmsgnums, content_type);
3648 /* get room record, obtaining a lock... */
3649 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
3650 syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
3652 if (need_to_free_re) regfree(&re);
3653 return (0); /* room not found */
3655 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
3657 if (cdbfr != NULL) {
3658 dellist = malloc(cdbfr->len);
3659 msglist = (long *) cdbfr->ptr;
3660 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
3661 num_msgs = cdbfr->len / sizeof(long);
3665 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
3666 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
3667 int have_more_del = 1;
3669 num_msgs = sort_msglist(msglist, num_msgs);
3670 if (num_dmsgnums > 1)
3671 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
3674 StrBuf *dbg = NewStrBuf();
3675 for (i = 0; i < num_dmsgnums; i++)
3676 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
3677 syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
3682 while ((i < num_msgs) && (have_more_del)) {
3685 /* Set/clear a bit for each criterion */
3687 /* 0 messages in the list or a null list means that we are
3688 * interested in deleting any messages which meet the other criteria.
3691 delete_this |= 0x01;
3694 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
3699 if (msglist[i] == dmsgnums[j]) {
3700 delete_this |= 0x01;
3703 have_more_del = (j < num_dmsgnums);
3706 if (have_contenttype) {
3707 GetMetaData(&smi, msglist[i]);
3708 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
3709 delete_this |= 0x02;
3712 delete_this |= 0x02;
3715 /* Delete message only if all bits are set */
3716 if (delete_this == 0x03) {
3717 dellist[num_deleted++] = msglist[i];
3724 StrBuf *dbg = NewStrBuf();
3725 for (i = 0; i < num_deleted; i++)
3726 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
3727 syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
3731 num_msgs = sort_msglist(msglist, num_msgs);
3732 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
3733 msglist, (int)(num_msgs * sizeof(long)));
3736 qrbuf.QRhighest = msglist[num_msgs - 1];
3738 qrbuf.QRhighest = 0;
3740 CtdlPutRoomLock(&qrbuf);
3742 /* Go through the messages we pulled out of the index, and decrement
3743 * their reference counts by 1. If this is the only room the message
3744 * was in, the reference count will reach zero and the message will
3745 * automatically be deleted from the database. We do this in a
3746 * separate pass because there might be plug-in hooks getting called,
3747 * and we don't want that happening during an S_ROOMS critical
3751 for (i=0; i<num_deleted; ++i) {
3752 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
3754 AdjRefCountList(dellist, num_deleted, -1);
3756 /* Now free the memory we used, and go away. */
3757 if (msglist != NULL) free(msglist);
3758 if (dellist != NULL) free(dellist);
3759 syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
3760 if (need_to_free_re) regfree(&re);
3761 return (num_deleted);
3768 * GetMetaData() - Get the supplementary record for a message
3770 void GetMetaData(struct MetaData *smibuf, long msgnum)
3773 struct cdbdata *cdbsmi;
3776 memset(smibuf, 0, sizeof(struct MetaData));
3777 smibuf->meta_msgnum = msgnum;
3778 smibuf->meta_refcount = 1; /* Default reference count is 1 */
3780 /* Use the negative of the message number for its supp record index */
3781 TheIndex = (0L - msgnum);
3783 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
3784 if (cdbsmi == NULL) {
3785 return; /* record not found; go with defaults */
3787 memcpy(smibuf, cdbsmi->ptr,
3788 ((cdbsmi->len > sizeof(struct MetaData)) ?
3789 sizeof(struct MetaData) : cdbsmi->len));
3796 * PutMetaData() - (re)write supplementary record for a message
3798 void PutMetaData(struct MetaData *smibuf)
3802 /* Use the negative of the message number for the metadata db index */
3803 TheIndex = (0L - smibuf->meta_msgnum);
3805 cdb_store(CDB_MSGMAIN,
3806 &TheIndex, (int)sizeof(long),
3807 smibuf, (int)sizeof(struct MetaData));
3812 * AdjRefCount - submit an adjustment to the reference count for a message.
3813 * (These are just queued -- we actually process them later.)
3815 void AdjRefCount(long msgnum, int incr)
3817 struct arcq new_arcq;
3820 syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
3822 begin_critical_section(S_SUPPMSGMAIN);
3823 if (arcfp == NULL) {
3824 arcfp = fopen(file_arcq, "ab+");
3825 chown(file_arcq, CTDLUID, (-1));
3826 chmod(file_arcq, 0600);
3828 end_critical_section(S_SUPPMSGMAIN);
3830 /* msgnum < 0 means that we're trying to close the file */
3832 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
3833 begin_critical_section(S_SUPPMSGMAIN);
3834 if (arcfp != NULL) {
3838 end_critical_section(S_SUPPMSGMAIN);
3843 * If we can't open the queue, perform the operation synchronously.
3845 if (arcfp == NULL) {
3846 TDAP_AdjRefCount(msgnum, incr);
3850 new_arcq.arcq_msgnum = msgnum;
3851 new_arcq.arcq_delta = incr;
3852 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
3854 syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3863 void AdjRefCountList(long *msgnum, long nmsg, int incr)
3865 long i, the_size, offset;
3866 struct arcq *new_arcq;
3869 syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
3871 begin_critical_section(S_SUPPMSGMAIN);
3872 if (arcfp == NULL) {
3873 arcfp = fopen(file_arcq, "ab+");
3874 chown(file_arcq, CTDLUID, (-1));
3875 chmod(file_arcq, 0600);
3877 end_critical_section(S_SUPPMSGMAIN);
3880 * If we can't open the queue, perform the operation synchronously.
3882 if (arcfp == NULL) {
3883 for (i = 0; i < nmsg; i++)
3884 TDAP_AdjRefCount(msgnum[i], incr);
3888 the_size = sizeof(struct arcq) * nmsg;
3889 new_arcq = malloc(the_size);
3890 for (i = 0; i < nmsg; i++) {
3891 new_arcq[i].arcq_msgnum = msgnum[i];
3892 new_arcq[i].arcq_delta = incr;
3896 while ((rv >= 0) && (offset < the_size))
3898 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
3900 syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
3916 * TDAP_ProcessAdjRefCountQueue()
3918 * Process the queue of message count adjustments that was created by calls
3919 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
3920 * for each one. This should be an "off hours" operation.
3922 int TDAP_ProcessAdjRefCountQueue(void)
3924 char file_arcq_temp[PATH_MAX];
3927 struct arcq arcq_rec;
3928 int num_records_processed = 0;
3930 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
3932 begin_critical_section(S_SUPPMSGMAIN);
3933 if (arcfp != NULL) {
3938 r = link(file_arcq, file_arcq_temp);
3940 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3941 end_critical_section(S_SUPPMSGMAIN);
3942 return(num_records_processed);
3946 end_critical_section(S_SUPPMSGMAIN);
3948 fp = fopen(file_arcq_temp, "rb");
3950 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3951 return(num_records_processed);
3954 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
3955 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
3956 ++num_records_processed;
3960 r = unlink(file_arcq_temp);
3962 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
3965 return(num_records_processed);
3971 * TDAP_AdjRefCount - adjust the reference count for a message.
3972 * This one does it "for real" because it's called by
3973 * the autopurger function that processes the queue
3974 * created by AdjRefCount(). If a message's reference
3975 * count becomes zero, we also delete the message from
3976 * disk and de-index it.
3978 void TDAP_AdjRefCount(long msgnum, int incr)
3981 struct MetaData smi;
3984 /* This is a *tight* critical section; please keep it that way, as
3985 * it may get called while nested in other critical sections.
3986 * Complicating this any further will surely cause deadlock!
3988 begin_critical_section(S_SUPPMSGMAIN);
3989 GetMetaData(&smi, msgnum);
3990 smi.meta_refcount += incr;
3992 end_critical_section(S_SUPPMSGMAIN);
3993 syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
3994 msgnum, incr, smi.meta_refcount
3997 /* If the reference count is now zero, delete the message
3998 * (and its supplementary record as well).
4000 if (smi.meta_refcount == 0) {
4001 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
4003 /* Call delete hooks with NULL room to show it has gone altogether */
4004 PerformDeleteHooks(NULL, msgnum);
4006 /* Remove from message base */
4008 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4009 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4011 /* Remove metadata record */
4012 delnum = (0L - msgnum);
4013 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4019 * Write a generic object to this room
4021 * Note: this could be much more efficient. Right now we use two temporary
4022 * files, and still pull the message into memory as with all others.
4024 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4025 char *content_type, /* MIME type of this object */
4026 char *raw_message, /* Data to be written */
4027 off_t raw_length, /* Size of raw_message */
4028 struct ctdluser *is_mailbox, /* Mailbox room? */
4029 int is_binary, /* Is encoding necessary? */
4030 int is_unique, /* Del others of this type? */
4031 unsigned int flags /* Internal save flags */
4034 struct ctdlroom qrbuf;
4035 char roomname[ROOMNAMELEN];
4036 struct CtdlMessage *msg;
4037 StrBuf *encoded_message = NULL;
4039 if (is_mailbox != NULL) {
4040 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4043 safestrncpy(roomname, req_room, sizeof(roomname));
4046 syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
4049 encoded_message = NewStrBufPlain(NULL, (size_t) (((raw_length * 134) / 100) + 4096 ) );
4052 encoded_message = NewStrBufPlain(NULL, (size_t)(raw_length + 4096));
4055 StrBufAppendBufPlain(encoded_message, HKEY("Content-type: "), 0);
4056 StrBufAppendBufPlain(encoded_message, content_type, -1, 0);
4057 StrBufAppendBufPlain(encoded_message, HKEY("\n"), 0);
4060 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: base64\n\n"), 0);
4063 StrBufAppendBufPlain(encoded_message, HKEY("Content-transfer-encoding: 7bit\n\n"), 0);
4067 StrBufBase64Append(encoded_message, NULL, raw_message, raw_length, 0);
4070 StrBufAppendBufPlain(encoded_message, raw_message, raw_length, 0);
4073 syslog(LOG_DEBUG, "Allocating\n");
4074 msg = malloc(sizeof(struct CtdlMessage));
4075 memset(msg, 0, sizeof(struct CtdlMessage));
4076 msg->cm_magic = CTDLMESSAGE_MAGIC;
4077 msg->cm_anon_type = MES_NORMAL;
4078 msg->cm_format_type = 4;
4079 CM_SetField(msg, eAuthor, CC->user.fullname, strlen(CC->user.fullname));
4080 CM_SetField(msg, eOriginalRoom, req_room, strlen(req_room));
4081 CM_SetField(msg, eNodeName, CtdlGetConfigStr("c_nodename"), strlen(CtdlGetConfigStr("c_nodename")));
4082 CM_SetField(msg, eHumanNode, CtdlGetConfigStr("c_humannode"), strlen(CtdlGetConfigStr("c_humannode")));
4083 msg->cm_flags = flags;
4085 CM_SetAsFieldSB(msg, eMesageText, &encoded_message);
4087 /* Create the requested room if we have to. */
4088 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4089 CtdlCreateRoom(roomname,
4090 ( (is_mailbox != NULL) ? 5 : 3 ),
4091 "", 0, 1, 0, VIEW_BBS);
4093 /* If the caller specified this object as unique, delete all
4094 * other objects of this type that are currently in the room.
4097 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
4098 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4101 /* Now write the data */
4102 CtdlSubmitMsg(msg, NULL, roomname, 0);
4108 /*****************************************************************************/
4109 /* MODULE INITIALIZATION STUFF */
4110 /*****************************************************************************/
4112 CTDL_MODULE_INIT(msgbase)
4115 FillMsgKeyLookupTable();
4118 /* return our Subversion id for the Log */