2 * Implements the message store.
4 * Copyright (c) 1987-2012 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License version 3.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
21 #if TIME_WITH_SYS_TIME
22 # include <sys/time.h>
26 # include <sys/time.h>
39 #include <sys/types.h>
44 #include <libcitadel.h>
47 #include "serv_extensions.h"
51 #include "sysdep_decls.h"
52 #include "citserver.h"
59 #include "internet_addressing.h"
60 #include "euidindex.h"
61 #include "journaling.h"
62 #include "citadel_dirs.h"
63 #include "clientsocket.h"
66 #include "ctdl_module.h"
69 struct addresses_to_be_filed *atbf = NULL;
71 /* This temp file holds the queue of operations for AdjRefCount() */
72 static FILE *arcfp = NULL;
73 void AdjRefCountList(long *msgnum, long nmsg, int incr);
75 int MessageDebugEnabled = 0;
78 * These are the four-character field headers we use when outputting
79 * messages in Citadel format (as opposed to RFC822 format).
82 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
83 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
84 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
85 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
86 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
87 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
88 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
89 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
119 eMsgField FieldOrder[] = {
120 /* Important fields */
131 /* Semi-important fields */
137 /* G is not used yet, may become virus signature*/
140 /* Q is not used yet */
143 /* X is not used yet */
144 /* Z is not used yet */
151 /* Message text (MUST be last) */
153 /* Not saved to disk:
158 static const long NDiskFields = sizeof(FieldOrder) / sizeof(eMsgField);
160 int CM_IsEmpty(struct CtdlMessage *Msg, eMsgField which)
162 return !((Msg->cm_fields[which] != NULL) &&
163 (Msg->cm_fields[which][0] != '\0'));
166 void CM_SetField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
168 if (Msg->cm_fields[which] != NULL)
169 free (Msg->cm_fields[which]);
170 Msg->cm_fields[which] = malloc(length + 1);
171 memcpy(Msg->cm_fields[which], buf, length);
172 Msg->cm_fields[which][length] = '\0';
175 void CM_SetFieldLONG(struct CtdlMessage *Msg, eMsgField which, long lvalue)
179 len = snprintf(buf, sizeof(buf), "%ld", lvalue);
180 CM_SetField(Msg, which, buf, len);
182 void CM_CutFieldAt(struct CtdlMessage *Msg, eMsgField WhichToCut, long maxlen)
184 if (Msg->cm_fields[WhichToCut] == NULL)
187 if (strlen(Msg->cm_fields[WhichToCut]) > maxlen)
188 Msg->cm_fields[WhichToCut][maxlen] = '\0';
191 void CM_FlushField(struct CtdlMessage *Msg, eMsgField which)
193 if (Msg->cm_fields[which] != NULL)
194 free (Msg->cm_fields[which]);
195 Msg->cm_fields[which] = NULL;
198 void CM_CopyField(struct CtdlMessage *Msg, eMsgField WhichToPutTo, eMsgField WhichtToCopy)
201 if (Msg->cm_fields[WhichToPutTo] != NULL)
202 free (Msg->cm_fields[WhichToPutTo]);
204 if (Msg->cm_fields[WhichtToCopy] != NULL)
206 len = strlen(Msg->cm_fields[WhichtToCopy]);
207 Msg->cm_fields[WhichToPutTo] = malloc(len + 1);
208 memcpy(Msg->cm_fields[WhichToPutTo], Msg->cm_fields[WhichToPutTo], len);
209 Msg->cm_fields[WhichToPutTo][len] = '\0';
212 Msg->cm_fields[WhichToPutTo] = NULL;
216 void CM_PrependToField(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
218 if (Msg->cm_fields[which] != NULL) {
223 oldmsgsize = strlen(Msg->cm_fields[which]) + 1;
224 newmsgsize = length + oldmsgsize;
226 new = malloc(newmsgsize);
227 memcpy(new, buf, length);
228 memcpy(new + length, Msg->cm_fields[which], oldmsgsize);
229 free(Msg->cm_fields[which]);
230 Msg->cm_fields[which] = new;
233 Msg->cm_fields[which] = malloc(length + 1);
234 memcpy(Msg->cm_fields[which], buf, length);
235 Msg->cm_fields[which][length] = '\0';
239 void CM_SetAsField(struct CtdlMessage *Msg, eMsgField which, char **buf, long length)
241 if (Msg->cm_fields[which] != NULL)
242 free (Msg->cm_fields[which]);
244 Msg->cm_fields[which] = *buf;
248 void CM_SetAsFieldSB(struct CtdlMessage *Msg, eMsgField which, StrBuf **buf)
250 if (Msg->cm_fields[which] != NULL)
251 free (Msg->cm_fields[which]);
253 Msg->cm_fields[which] = SmashStrBuf(buf);
256 void CM_GetAsField(struct CtdlMessage *Msg, eMsgField which, char **ret, long *retlen)
258 if (Msg->cm_fields[which] != NULL)
260 *retlen = strlen(Msg->cm_fields[which]);
261 *ret = Msg->cm_fields[which];
262 Msg->cm_fields[which] = NULL;
272 * Returns 1 if the supplied pointer points to a valid Citadel message.
273 * If the pointer is NULL or the magic number check fails, returns 0.
275 int CM_IsValidMsg(struct CtdlMessage *msg) {
278 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
279 struct CitContext *CCC = CC;
280 MSGM_syslog(LOG_WARNING, "CM_IsValidMsg() -- self-check failed\n");
286 void CM_FreeContents(struct CtdlMessage *msg)
290 for (i = 0; i < 256; ++i)
291 if (msg->cm_fields[i] != NULL) {
292 free(msg->cm_fields[i]);
295 msg->cm_magic = 0; /* just in case */
298 * 'Destructor' for struct CtdlMessage
300 void CM_Free(struct CtdlMessage *msg)
302 if (CM_IsValidMsg(msg) == 0)
304 if (msg != NULL) free (msg);
307 CM_FreeContents(msg);
311 int CM_DupField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
314 len = strlen(OrgMsg->cm_fields[i]);
315 NewMsg->cm_fields[i] = malloc(len + 1);
316 if (NewMsg->cm_fields[i] == NULL)
318 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
319 NewMsg->cm_fields[i][len] = '\0';
323 struct CtdlMessage * CM_Duplicate(struct CtdlMessage *OrgMsg)
326 struct CtdlMessage *NewMsg;
328 if (CM_IsValidMsg(OrgMsg) == 0)
330 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
334 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
336 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
338 for (i = 0; i < 256; ++i)
340 if (OrgMsg->cm_fields[i] != NULL)
342 if (!CM_DupField(i, OrgMsg, NewMsg))
356 * This function is self explanatory.
357 * (What can I say, I'm in a weird mood today...)
359 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
363 for (i = 0; i < strlen(name); ++i) {
364 if (name[i] == '@') {
365 while (isspace(name[i - 1]) && i > 0) {
366 strcpy(&name[i - 1], &name[i]);
369 while (isspace(name[i + 1])) {
370 strcpy(&name[i + 1], &name[i + 2]);
378 * Aliasing for network mail.
379 * (Error messages have been commented out, because this is a server.)
381 int alias(char *name)
382 { /* process alias and routing info for mail */
383 struct CitContext *CCC = CC;
386 char aaa[SIZ], bbb[SIZ];
387 char *ignetcfg = NULL;
388 char *ignetmap = NULL;
394 char original_name[256];
395 safestrncpy(original_name, name, sizeof original_name);
398 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
399 stripallbut(name, '<', '>');
401 fp = fopen(file_mail_aliases, "r");
403 fp = fopen("/dev/null", "r");
410 while (fgets(aaa, sizeof aaa, fp) != NULL) {
411 while (isspace(name[0]))
412 strcpy(name, &name[1]);
413 aaa[strlen(aaa) - 1] = 0;
415 for (a = 0; a < strlen(aaa); ++a) {
417 strcpy(bbb, &aaa[a + 1]);
421 if (!strcasecmp(name, aaa))
426 /* Hit the Global Address Book */
427 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
431 if (strcasecmp(original_name, name)) {
432 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
435 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
436 for (a=0; a<strlen(name); ++a) {
437 if (name[a] == '@') {
438 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
440 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
445 /* determine local or remote type, see citadel.h */
446 at = haschar(name, '@');
447 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
448 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
449 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
451 /* figure out the delivery mode */
452 extract_token(node, name, 1, '@', sizeof node);
454 /* If there are one or more dots in the nodename, we assume that it
455 * is an FQDN and will attempt SMTP delivery to the Internet.
457 if (haschar(node, '.') > 0) {
458 return(MES_INTERNET);
461 /* Otherwise we look in the IGnet maps for a valid Citadel node.
462 * Try directly-connected nodes first...
464 ignetcfg = CtdlGetSysConfig(IGNETCFG);
465 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
466 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
467 extract_token(testnode, buf, 0, '|', sizeof testnode);
468 if (!strcasecmp(node, testnode)) {
476 * Then try nodes that are two or more hops away.
478 ignetmap = CtdlGetSysConfig(IGNETMAP);
479 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
480 extract_token(buf, ignetmap, i, '\n', sizeof buf);
481 extract_token(testnode, buf, 0, '|', sizeof testnode);
482 if (!strcasecmp(node, testnode)) {
489 /* If we get to this point it's an invalid node name */
495 * Back end for the MSGS command: output message number only.
497 void simple_listing(long msgnum, void *userdata)
499 cprintf("%ld\n", msgnum);
505 * Back end for the MSGS command: output header summary.
507 void headers_listing(long msgnum, void *userdata)
509 struct CtdlMessage *msg;
511 msg = CtdlFetchMessage(msgnum, 0);
513 cprintf("%ld|0|||||\n", msgnum);
517 cprintf("%ld|%s|%s|%s|%s|%s|\n",
519 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
520 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
521 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
522 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
523 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
529 * Back end for the MSGS command: output EUID header.
531 void headers_euid(long msgnum, void *userdata)
533 struct CtdlMessage *msg;
535 msg = CtdlFetchMessage(msgnum, 0);
537 cprintf("%ld||\n", msgnum);
541 cprintf("%ld|%s|%s\n",
543 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
544 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
552 /* Determine if a given message matches the fields in a message template.
553 * Return 0 for a successful match.
555 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
558 /* If there aren't any fields in the template, all messages will
561 if (template == NULL) return(0);
563 /* Null messages are bogus. */
564 if (msg == NULL) return(1);
566 for (i='A'; i<='Z'; ++i) {
567 if (template->cm_fields[i] != NULL) {
568 if (msg->cm_fields[i] == NULL) {
569 /* Considered equal if temmplate is empty string */
570 if (IsEmptyStr(template->cm_fields[i])) continue;
573 if (strcasecmp(msg->cm_fields[i],
574 template->cm_fields[i])) return 1;
578 /* All compares succeeded: we have a match! */
585 * Retrieve the "seen" message list for the current room.
587 void CtdlGetSeen(char *buf, int which_set) {
588 struct CitContext *CCC = CC;
591 /* Learn about the user and room in question */
592 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
594 if (which_set == ctdlsetseen_seen)
595 safestrncpy(buf, vbuf.v_seen, SIZ);
596 if (which_set == ctdlsetseen_answered)
597 safestrncpy(buf, vbuf.v_answered, SIZ);
603 * Manipulate the "seen msgs" string (or other message set strings)
605 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
606 int target_setting, int which_set,
607 struct ctdluser *which_user, struct ctdlroom *which_room) {
608 struct CitContext *CCC = CC;
609 struct cdbdata *cdbfr;
614 long hi = (-1L); /// TODO: we just write here. y?
623 char *is_set; /* actually an array of booleans */
625 /* Don't bother doing *anything* if we were passed a list of zero messages */
626 if (num_target_msgnums < 1) {
630 /* If no room was specified, we go with the current room. */
632 which_room = &CCC->room;
635 /* If no user was specified, we go with the current user. */
637 which_user = &CCC->user;
640 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
641 num_target_msgnums, target_msgnums[0],
642 (target_setting ? "SET" : "CLEAR"),
646 /* Learn about the user and room in question */
647 CtdlGetRelationship(&vbuf, which_user, which_room);
649 /* Load the message list */
650 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
652 msglist = (long *) cdbfr->ptr;
653 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
654 num_msgs = cdbfr->len / sizeof(long);
657 return; /* No messages at all? No further action. */
660 is_set = malloc(num_msgs * sizeof(char));
661 memset(is_set, 0, (num_msgs * sizeof(char)) );
663 /* Decide which message set we're manipulating */
665 case ctdlsetseen_seen:
666 vset = NewStrBufPlain(vbuf.v_seen, -1);
668 case ctdlsetseen_answered:
669 vset = NewStrBufPlain(vbuf.v_answered, -1);
676 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
677 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
678 for (i=0; i<num_msgs; ++i) {
679 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
681 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
682 for (k=0; k<num_target_msgnums; ++k) {
683 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
687 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
689 /* Translate the existing sequence set into an array of booleans */
690 setstr = NewStrBuf();
694 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
696 StrBufExtract_token(lostr, setstr, 0, ':');
697 if (StrBufNum_tokens(setstr, ':') >= 2) {
698 StrBufExtract_token(histr, setstr, 1, ':');
702 StrBufAppendBuf(histr, lostr, 0);
705 if (!strcmp(ChrPtr(histr), "*")) {
712 for (i = 0; i < num_msgs; ++i) {
713 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
723 /* Now translate the array of booleans back into a sequence set */
729 for (i=0; i<num_msgs; ++i) {
733 for (k=0; k<num_target_msgnums; ++k) {
734 if (msglist[i] == target_msgnums[k]) {
735 is_seen = target_setting;
739 if ((was_seen == 0) && (is_seen == 1)) {
742 else if ((was_seen == 1) && (is_seen == 0)) {
745 if (StrLength(vset) > 0) {
746 StrBufAppendBufPlain(vset, HKEY(","), 0);
749 StrBufAppendPrintf(vset, "%ld", hi);
752 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
756 if ((is_seen) && (i == num_msgs - 1)) {
757 if (StrLength(vset) > 0) {
758 StrBufAppendBufPlain(vset, HKEY(","), 0);
760 if ((i==0) || (was_seen == 0)) {
761 StrBufAppendPrintf(vset, "%ld", msglist[i]);
764 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
772 * We will have to stuff this string back into a 4096 byte buffer, so if it's
773 * larger than that now, truncate it by removing tokens from the beginning.
774 * The limit of 100 iterations is there to prevent an infinite loop in case
775 * something unexpected happens.
777 int number_of_truncations = 0;
778 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
779 StrBufRemove_token(vset, 0, ',');
780 ++number_of_truncations;
784 * If we're truncating the sequence set of messages marked with the 'seen' flag,
785 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
786 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
788 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
790 first_tok = NewStrBuf();
791 StrBufExtract_token(first_tok, vset, 0, ',');
792 StrBufRemove_token(vset, 0, ',');
794 if (StrBufNum_tokens(first_tok, ':') > 1) {
795 StrBufRemove_token(first_tok, 0, ':');
799 new_set = NewStrBuf();
800 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
801 StrBufAppendBuf(new_set, first_tok, 0);
802 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
803 StrBufAppendBuf(new_set, vset, 0);
806 FreeStrBuf(&first_tok);
810 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
812 /* Decide which message set we're manipulating */
814 case ctdlsetseen_seen:
815 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
817 case ctdlsetseen_answered:
818 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
824 CtdlSetRelationship(&vbuf, which_user, which_room);
830 * API function to perform an operation for each qualifying message in the
831 * current room. (Returns the number of messages processed.)
833 int CtdlForEachMessage(int mode, long ref, char *search_string,
835 struct CtdlMessage *compare,
836 ForEachMsgCallback CallBack,
839 struct CitContext *CCC = CC;
842 struct cdbdata *cdbfr;
843 long *msglist = NULL;
845 int num_processed = 0;
848 struct CtdlMessage *msg = NULL;
851 int printed_lastold = 0;
852 int num_search_msgs = 0;
853 long *search_msgs = NULL;
855 int need_to_free_re = 0;
858 if ((content_type) && (!IsEmptyStr(content_type))) {
859 regcomp(&re, content_type, 0);
863 /* Learn about the user and room in question */
864 if (server_shutting_down) {
865 if (need_to_free_re) regfree(&re);
868 CtdlGetUser(&CCC->user, CCC->curr_user);
870 if (server_shutting_down) {
871 if (need_to_free_re) regfree(&re);
874 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
876 if (server_shutting_down) {
877 if (need_to_free_re) regfree(&re);
881 /* Load the message list */
882 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
884 if (need_to_free_re) regfree(&re);
885 return 0; /* No messages at all? No further action. */
888 msglist = (long *) cdbfr->ptr;
889 num_msgs = cdbfr->len / sizeof(long);
891 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
892 cdb_free(cdbfr); /* we own this memory now */
895 * Now begin the traversal.
897 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
899 /* If the caller is looking for a specific MIME type, filter
900 * out all messages which are not of the type requested.
902 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
904 /* This call to GetMetaData() sits inside this loop
905 * so that we only do the extra database read per msg
906 * if we need to. Doing the extra read all the time
907 * really kills the server. If we ever need to use
908 * metadata for another search criterion, we need to
909 * move the read somewhere else -- but still be smart
910 * enough to only do the read if the caller has
911 * specified something that will need it.
913 if (server_shutting_down) {
914 if (need_to_free_re) regfree(&re);
918 GetMetaData(&smi, msglist[a]);
920 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
921 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
927 num_msgs = sort_msglist(msglist, num_msgs);
929 /* If a template was supplied, filter out the messages which
930 * don't match. (This could induce some delays!)
933 if (compare != NULL) {
934 for (a = 0; a < num_msgs; ++a) {
935 if (server_shutting_down) {
936 if (need_to_free_re) regfree(&re);
940 msg = CtdlFetchMessage(msglist[a], 1);
942 if (CtdlMsgCmp(msg, compare)) {
951 /* If a search string was specified, get a message list from
952 * the full text index and remove messages which aren't on both
956 * Since the lists are sorted and strictly ascending, and the
957 * output list is guaranteed to be shorter than or equal to the
958 * input list, we overwrite the bottom of the input list. This
959 * eliminates the need to memmove big chunks of the list over and
962 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
964 /* Call search module via hook mechanism.
965 * NULL means use any search function available.
966 * otherwise replace with a char * to name of search routine
968 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
970 if (num_search_msgs > 0) {
974 orig_num_msgs = num_msgs;
976 for (i=0; i<orig_num_msgs; ++i) {
977 for (j=0; j<num_search_msgs; ++j) {
978 if (msglist[i] == search_msgs[j]) {
979 msglist[num_msgs++] = msglist[i];
985 num_msgs = 0; /* No messages qualify */
987 if (search_msgs != NULL) free(search_msgs);
989 /* Now that we've purged messages which don't contain the search
990 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
997 * Now iterate through the message list, according to the
998 * criteria supplied by the caller.
1001 for (a = 0; a < num_msgs; ++a) {
1002 if (server_shutting_down) {
1003 if (need_to_free_re) regfree(&re);
1005 return num_processed;
1007 thismsg = msglist[a];
1008 if (mode == MSGS_ALL) {
1012 is_seen = is_msg_in_sequence_set(
1013 vbuf.v_seen, thismsg);
1014 if (is_seen) lastold = thismsg;
1020 || ((mode == MSGS_OLD) && (is_seen))
1021 || ((mode == MSGS_NEW) && (!is_seen))
1022 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
1023 || ((mode == MSGS_FIRST) && (a < ref))
1024 || ((mode == MSGS_GT) && (thismsg > ref))
1025 || ((mode == MSGS_LT) && (thismsg < ref))
1026 || ((mode == MSGS_EQ) && (thismsg == ref))
1029 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
1031 CallBack(lastold, userdata);
1032 printed_lastold = 1;
1035 if (CallBack) CallBack(thismsg, userdata);
1039 if (need_to_free_re) regfree(&re);
1042 * We cache the most recent msglist in order to do security checks later
1044 if (CCC->client_socket > 0) {
1045 if (CCC->cached_msglist != NULL) {
1046 free(CCC->cached_msglist);
1048 CCC->cached_msglist = msglist;
1049 CCC->cached_num_msgs = num_msgs;
1055 return num_processed;
1061 * cmd_msgs() - get list of message #'s in this room
1062 * implements the MSGS server command using CtdlForEachMessage()
1064 void cmd_msgs(char *cmdbuf)
1073 int with_template = 0;
1074 struct CtdlMessage *template = NULL;
1075 char search_string[1024];
1076 ForEachMsgCallback CallBack;
1078 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
1080 extract_token(which, cmdbuf, 0, '|', sizeof which);
1081 cm_ref = extract_int(cmdbuf, 1);
1082 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
1083 with_template = extract_int(cmdbuf, 2);
1084 switch (extract_int(cmdbuf, 3))
1087 case MSG_HDRS_BRIEF:
1088 CallBack = simple_listing;
1091 CallBack = headers_listing;
1094 CallBack = headers_euid;
1099 if (!strncasecmp(which, "OLD", 3))
1101 else if (!strncasecmp(which, "NEW", 3))
1103 else if (!strncasecmp(which, "FIRST", 5))
1105 else if (!strncasecmp(which, "LAST", 4))
1107 else if (!strncasecmp(which, "GT", 2))
1109 else if (!strncasecmp(which, "LT", 2))
1111 else if (!strncasecmp(which, "SEARCH", 6))
1116 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
1117 cprintf("%d Full text index is not enabled on this server.\n",
1118 ERROR + CMD_NOT_SUPPORTED);
1122 if (with_template) {
1124 cprintf("%d Send template then receive message list\n",
1126 template = (struct CtdlMessage *)
1127 malloc(sizeof(struct CtdlMessage));
1128 memset(template, 0, sizeof(struct CtdlMessage));
1129 template->cm_magic = CTDLMESSAGE_MAGIC;
1130 template->cm_anon_type = MES_NORMAL;
1132 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
1134 extract_token(tfield, buf, 0, '|', sizeof tfield);
1135 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
1136 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
1137 if (!strcasecmp(tfield, msgkeys[i])) {
1138 CM_SetField(template, i, tvalue, tValueLen);
1145 cprintf("%d \n", LISTING_FOLLOWS);
1148 CtdlForEachMessage(mode,
1149 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
1150 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
1155 if (template != NULL) CM_Free(template);
1161 * memfmout() - Citadel text formatter and paginator.
1162 * Although the original purpose of this routine was to format
1163 * text to the reader's screen width, all we're really using it
1164 * for here is to format text out to 80 columns before sending it
1165 * to the client. The client software may reformat it again.
1168 char *mptr, /* where are we going to get our text from? */
1169 const char *nl /* string to terminate lines with */
1171 struct CitContext *CCC = CC;
1173 unsigned char ch = 0;
1180 while (ch=*(mptr++), ch != 0) {
1183 if (client_write(outbuf, len) == -1)
1185 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1189 if (client_write(nl, nllen) == -1)
1191 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1196 else if (ch == '\r') {
1197 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1199 else if (isspace(ch)) {
1200 if (column > 72) { /* Beyond 72 columns, break on the next space */
1201 if (client_write(outbuf, len) == -1)
1203 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1207 if (client_write(nl, nllen) == -1)
1209 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1222 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1223 if (client_write(outbuf, len) == -1)
1225 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1229 if (client_write(nl, nllen) == -1)
1231 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1239 if (client_write(outbuf, len) == -1)
1241 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1245 client_write(nl, nllen);
1253 * Callback function for mime parser that simply lists the part
1255 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1256 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1257 char *cbid, void *cbuserdata)
1261 ma = (struct ma_info *)cbuserdata;
1262 if (ma->is_ma == 0) {
1263 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1276 * Callback function for multipart prefix
1278 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1279 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1280 char *cbid, void *cbuserdata)
1284 ma = (struct ma_info *)cbuserdata;
1285 if (!strcasecmp(cbtype, "multipart/alternative")) {
1289 if (ma->is_ma == 0) {
1290 cprintf("pref=%s|%s\n", partnum, cbtype);
1295 * Callback function for multipart sufffix
1297 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1298 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1299 char *cbid, void *cbuserdata)
1303 ma = (struct ma_info *)cbuserdata;
1304 if (ma->is_ma == 0) {
1305 cprintf("suff=%s|%s\n", partnum, cbtype);
1307 if (!strcasecmp(cbtype, "multipart/alternative")) {
1314 * Callback function for mime parser that opens a section for downloading
1316 void mime_download(char *name, char *filename, char *partnum, char *disp,
1317 void *content, char *cbtype, char *cbcharset, size_t length,
1318 char *encoding, char *cbid, void *cbuserdata)
1321 CitContext *CCC = MyContext();
1323 /* Silently go away if there's already a download open. */
1324 if (CCC->download_fp != NULL)
1328 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1329 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1331 CCC->download_fp = tmpfile();
1332 if (CCC->download_fp == NULL) {
1333 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1335 cprintf("%d cannot open temporary file: %s\n",
1336 ERROR + INTERNAL_ERROR, strerror(errno));
1340 rv = fwrite(content, length, 1, CCC->download_fp);
1342 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1344 cprintf("%d unable to write tempfile.\n",
1346 fclose(CCC->download_fp);
1347 CCC->download_fp = NULL;
1350 fflush(CCC->download_fp);
1351 rewind(CCC->download_fp);
1353 OpenCmdResult(filename, cbtype);
1360 * Callback function for mime parser that outputs a section all at once.
1361 * We can specify the desired section by part number *or* content-id.
1363 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1364 void *content, char *cbtype, char *cbcharset, size_t length,
1365 char *encoding, char *cbid, void *cbuserdata)
1367 int *found_it = (int *)cbuserdata;
1370 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1371 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1374 cprintf("%d %d|-1|%s|%s|%s\n",
1381 client_write(content, length);
1387 * Load a message from disk into memory.
1388 * This is used by CtdlOutputMsg() and other fetch functions.
1390 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1391 * using the CtdlMessageFree() function.
1393 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1395 struct CitContext *CCC = CC;
1396 struct cdbdata *dmsgtext;
1397 struct CtdlMessage *ret = NULL;
1401 cit_uint8_t field_header;
1403 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1404 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1405 if (dmsgtext == NULL) {
1406 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1409 mptr = dmsgtext->ptr;
1410 upper_bound = mptr + dmsgtext->len;
1412 /* Parse the three bytes that begin EVERY message on disk.
1413 * The first is always 0xFF, the on-disk magic number.
1414 * The second is the anonymous/public type byte.
1415 * The third is the format type byte (vari, fixed, or MIME).
1419 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1423 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1424 memset(ret, 0, sizeof(struct CtdlMessage));
1426 ret->cm_magic = CTDLMESSAGE_MAGIC;
1427 ret->cm_anon_type = *mptr++; /* Anon type byte */
1428 ret->cm_format_type = *mptr++; /* Format type byte */
1431 * The rest is zero or more arbitrary fields. Load them in.
1432 * We're done when we encounter either a zero-length field or
1433 * have just processed the 'M' (message text) field.
1437 if (mptr >= upper_bound) {
1440 field_header = *mptr++;
1442 CM_SetField(ret, field_header, mptr, len);
1444 mptr += len + 1; /* advance to next field */
1446 } while ((mptr < upper_bound) && (field_header != 'M'));
1450 /* Always make sure there's something in the msg text field. If
1451 * it's NULL, the message text is most likely stored separately,
1452 * so go ahead and fetch that. Failing that, just set a dummy
1453 * body so other code doesn't barf.
1455 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1456 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1457 if (dmsgtext != NULL) {
1458 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1462 if (CM_IsEmpty(ret, eMesageText)) {
1463 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1466 /* Perform "before read" hooks (aborting if any return nonzero) */
1467 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1478 * Pre callback function for multipart/alternative
1480 * NOTE: this differs from the standard behavior for a reason. Normally when
1481 * displaying multipart/alternative you want to show the _last_ usable
1482 * format in the message. Here we show the _first_ one, because it's
1483 * usually text/plain. Since this set of functions is designed for text
1484 * output to non-MIME-aware clients, this is the desired behavior.
1487 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1488 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1489 char *cbid, void *cbuserdata)
1491 struct CitContext *CCC = CC;
1494 ma = (struct ma_info *)cbuserdata;
1495 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1496 if (!strcasecmp(cbtype, "multipart/alternative")) {
1500 if (!strcasecmp(cbtype, "message/rfc822")) {
1506 * Post callback function for multipart/alternative
1508 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1509 void *content, char *cbtype, char *cbcharset, size_t length,
1510 char *encoding, char *cbid, void *cbuserdata)
1512 struct CitContext *CCC = CC;
1515 ma = (struct ma_info *)cbuserdata;
1516 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1517 if (!strcasecmp(cbtype, "multipart/alternative")) {
1521 if (!strcasecmp(cbtype, "message/rfc822")) {
1527 * Inline callback function for mime parser that wants to display text
1529 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1530 void *content, char *cbtype, char *cbcharset, size_t length,
1531 char *encoding, char *cbid, void *cbuserdata)
1533 struct CitContext *CCC = CC;
1539 ma = (struct ma_info *)cbuserdata;
1541 MSG_syslog(LOG_DEBUG,
1542 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1543 partnum, filename, cbtype, (long)length);
1546 * If we're in the middle of a multipart/alternative scope and
1547 * we've already printed another section, skip this one.
1549 if ( (ma->is_ma) && (ma->did_print) ) {
1550 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1555 if ( (!strcasecmp(cbtype, "text/plain"))
1556 || (IsEmptyStr(cbtype)) ) {
1559 client_write(wptr, length);
1560 if (wptr[length-1] != '\n') {
1567 if (!strcasecmp(cbtype, "text/html")) {
1568 ptr = html_to_ascii(content, length, 80, 0);
1570 client_write(ptr, wlen);
1571 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1578 if (ma->use_fo_hooks) {
1579 if (PerformFixedOutputHooks(cbtype, content, length)) {
1580 /* above function returns nonzero if it handled the part */
1585 if (strncasecmp(cbtype, "multipart/", 10)) {
1586 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1587 partnum, filename, cbtype, (long)length);
1593 * The client is elegant and sophisticated and wants to be choosy about
1594 * MIME content types, so figure out which multipart/alternative part
1595 * we're going to send.
1597 * We use a system of weights. When we find a part that matches one of the
1598 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1599 * and then set ma->chosen_pref to that MIME type's position in our preference
1600 * list. If we then hit another match, we only replace the first match if
1601 * the preference value is lower.
1603 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1604 void *content, char *cbtype, char *cbcharset, size_t length,
1605 char *encoding, char *cbid, void *cbuserdata)
1607 struct CitContext *CCC = CC;
1612 ma = (struct ma_info *)cbuserdata;
1614 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1615 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1616 // I don't know if there are any side effects! Please TEST TEST TEST
1617 //if (ma->is_ma > 0) {
1619 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1620 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1621 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1622 if (i < ma->chosen_pref) {
1623 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1624 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1625 ma->chosen_pref = i;
1632 * Now that we've chosen our preferred part, output it.
1634 void output_preferred(char *name,
1646 struct CitContext *CCC = CC;
1649 int add_newline = 0;
1652 char *decoded = NULL;
1653 size_t bytes_decoded;
1656 ma = (struct ma_info *)cbuserdata;
1658 /* This is not the MIME part you're looking for... */
1659 if (strcasecmp(partnum, ma->chosen_part)) return;
1661 /* If the content-type of this part is in our preferred formats
1662 * list, we can simply output it verbatim.
1664 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1665 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1666 if (!strcasecmp(buf, cbtype)) {
1667 /* Yeah! Go! W00t!! */
1668 if (ma->dont_decode == 0)
1669 rc = mime_decode_now (content,
1675 break; /* Give us the chance, maybe theres another one. */
1677 if (rc == 0) text_content = (char *)content;
1679 text_content = decoded;
1680 length = bytes_decoded;
1683 if (text_content[length-1] != '\n') {
1686 cprintf("Content-type: %s", cbtype);
1687 if (!IsEmptyStr(cbcharset)) {
1688 cprintf("; charset=%s", cbcharset);
1690 cprintf("\nContent-length: %d\n",
1691 (int)(length + add_newline) );
1692 if (!IsEmptyStr(encoding)) {
1693 cprintf("Content-transfer-encoding: %s\n", encoding);
1696 cprintf("Content-transfer-encoding: 7bit\n");
1698 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1700 if (client_write(text_content, length) == -1)
1702 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1705 if (add_newline) cprintf("\n");
1706 if (decoded != NULL) free(decoded);
1711 /* No translations required or possible: output as text/plain */
1712 cprintf("Content-type: text/plain\n\n");
1714 if (ma->dont_decode == 0)
1715 rc = mime_decode_now (content,
1721 return; /* Give us the chance, maybe theres another one. */
1723 if (rc == 0) text_content = (char *)content;
1725 text_content = decoded;
1726 length = bytes_decoded;
1729 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1730 length, encoding, cbid, cbuserdata);
1731 if (decoded != NULL) free(decoded);
1736 char desired_section[64];
1743 * Callback function for
1745 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1746 void *content, char *cbtype, char *cbcharset, size_t length,
1747 char *encoding, char *cbid, void *cbuserdata)
1749 struct encapmsg *encap;
1751 encap = (struct encapmsg *)cbuserdata;
1753 /* Only proceed if this is the desired section... */
1754 if (!strcasecmp(encap->desired_section, partnum)) {
1755 encap->msglen = length;
1756 encap->msg = malloc(length + 2);
1757 memcpy(encap->msg, content, length);
1764 * Determine whether the specified message exists in the cached_msglist
1765 * (This is a security check)
1767 int check_cached_msglist(long msgnum) {
1768 struct CitContext *CCC = CC;
1770 /* cases in which we skip the check */
1771 if (!CCC) return om_ok; /* not a session */
1772 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1773 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1774 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1777 /* Do a binary search within the cached_msglist for the requested msgnum */
1779 int max = (CC->cached_num_msgs - 1);
1781 while (max >= min) {
1782 int middle = min + (max-min) / 2 ;
1783 if (msgnum == CCC->cached_msglist[middle]) {
1786 if (msgnum > CC->cached_msglist[middle]) {
1794 return om_access_denied;
1800 * Get a message off disk. (returns om_* values found in msgbase.h)
1803 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1804 int mode, /* how would you like that message? */
1805 int headers_only, /* eschew the message body? */
1806 int do_proto, /* do Citadel protocol responses? */
1807 int crlf, /* Use CRLF newlines instead of LF? */
1808 char *section, /* NULL or a message/rfc822 section */
1809 int flags, /* various flags; see msgbase.h */
1813 struct CitContext *CCC = CC;
1814 struct CtdlMessage *TheMessage = NULL;
1815 int retcode = CIT_OK;
1816 struct encapmsg encap;
1819 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1821 (section ? section : "<>")
1824 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1827 if (r == om_not_logged_in) {
1828 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1831 cprintf("%d An unknown error has occurred.\n", ERROR);
1838 * Check to make sure the message is actually IN this room
1840 r = check_cached_msglist(msg_num);
1841 if (r == om_access_denied) {
1842 /* Not in the cache? We get ONE shot to check it again. */
1843 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1844 r = check_cached_msglist(msg_num);
1847 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1848 msg_num, CCC->room.QRname
1851 if (r == om_access_denied) {
1852 cprintf("%d message %ld was not found in this room\n",
1853 ERROR + HIGHER_ACCESS_REQUIRED,
1862 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1863 * request that we don't even bother loading the body into memory.
1865 if (headers_only == HEADERS_FAST) {
1866 TheMessage = CtdlFetchMessage(msg_num, 0);
1869 TheMessage = CtdlFetchMessage(msg_num, 1);
1872 if (TheMessage == NULL) {
1873 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1874 ERROR + MESSAGE_NOT_FOUND, msg_num);
1875 return(om_no_such_msg);
1878 /* Here is the weird form of this command, to process only an
1879 * encapsulated message/rfc822 section.
1881 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1882 memset(&encap, 0, sizeof encap);
1883 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1884 mime_parser(TheMessage->cm_fields[eMesageText],
1886 *extract_encapsulated_message,
1887 NULL, NULL, (void *)&encap, 0
1890 if ((Author != NULL) && (*Author == NULL))
1892 *Author = TheMessage->cm_fields[eAuthor];
1893 TheMessage->cm_fields[eAuthor] = NULL;
1895 if ((Address != NULL) && (*Address == NULL))
1897 *Address = TheMessage->cm_fields[erFc822Addr];
1898 TheMessage->cm_fields[erFc822Addr] = NULL;
1900 CM_Free(TheMessage);
1904 encap.msg[encap.msglen] = 0;
1905 TheMessage = convert_internet_message(encap.msg);
1906 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1908 /* Now we let it fall through to the bottom of this
1909 * function, because TheMessage now contains the
1910 * encapsulated message instead of the top-level
1911 * message. Isn't that neat?
1916 cprintf("%d msg %ld has no part %s\n",
1917 ERROR + MESSAGE_NOT_FOUND,
1921 retcode = om_no_such_msg;
1926 /* Ok, output the message now */
1927 if (retcode == CIT_OK)
1928 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1929 if ((Author != NULL) && (*Author == NULL))
1931 *Author = TheMessage->cm_fields[eAuthor];
1932 TheMessage->cm_fields[eAuthor] = NULL;
1934 if ((Address != NULL) && (*Address == NULL))
1936 *Address = TheMessage->cm_fields[erFc822Addr];
1937 TheMessage->cm_fields[erFc822Addr] = NULL;
1940 CM_Free(TheMessage);
1946 char *qp_encode_email_addrs(char *source)
1948 struct CitContext *CCC = CC;
1949 char *user, *node, *name;
1950 const char headerStr[] = "=?UTF-8?Q?";
1954 int need_to_encode = 0;
1960 long nAddrPtrMax = 50;
1965 if (source == NULL) return source;
1966 if (IsEmptyStr(source)) return source;
1967 if (MessageDebugEnabled != 0) cit_backtrace();
1968 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1970 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1971 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1972 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1975 while (!IsEmptyStr (&source[i])) {
1976 if (nColons >= nAddrPtrMax){
1979 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1980 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1981 free (AddrPtr), AddrPtr = ptr;
1983 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1984 memset(&ptr[nAddrPtrMax], 0,
1985 sizeof (long) * nAddrPtrMax);
1987 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1988 free (AddrUtf8), AddrUtf8 = ptr;
1991 if (((unsigned char) source[i] < 32) ||
1992 ((unsigned char) source[i] > 126)) {
1994 AddrUtf8[nColons] = 1;
1996 if (source[i] == '"')
1997 InQuotes = !InQuotes;
1998 if (!InQuotes && source[i] == ',') {
1999 AddrPtr[nColons] = i;
2004 if (need_to_encode == 0) {
2011 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
2012 Encoded = (char*) malloc (EncodedMaxLen);
2014 for (i = 0; i < nColons; i++)
2015 source[AddrPtr[i]++] = '\0';
2016 /* TODO: if libidn, this might get larger*/
2017 user = malloc(SourceLen + 1);
2018 node = malloc(SourceLen + 1);
2019 name = malloc(SourceLen + 1);
2023 for (i = 0; i < nColons && nPtr != NULL; i++) {
2024 nmax = EncodedMaxLen - (nPtr - Encoded);
2026 process_rfc822_addr(&source[AddrPtr[i]],
2030 /* TODO: libIDN here ! */
2031 if (IsEmptyStr(name)) {
2032 n = snprintf(nPtr, nmax,
2033 (i==0)?"%s@%s" : ",%s@%s",
2037 EncodedName = rfc2047encode(name, strlen(name));
2038 n = snprintf(nPtr, nmax,
2039 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
2040 EncodedName, user, node);
2045 n = snprintf(nPtr, nmax,
2046 (i==0)?"%s" : ",%s",
2047 &source[AddrPtr[i]]);
2053 ptr = (char*) malloc(EncodedMaxLen * 2);
2054 memcpy(ptr, Encoded, EncodedMaxLen);
2055 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
2056 free(Encoded), Encoded = ptr;
2058 i--; /* do it once more with properly lengthened buffer */
2061 for (i = 0; i < nColons; i++)
2062 source[--AddrPtr[i]] = ',';
2073 /* If the last item in a list of recipients was truncated to a partial address,
2074 * remove it completely in order to avoid choking libSieve
2076 void sanitize_truncated_recipient(char *str)
2079 if (num_tokens(str, ',') < 2) return;
2081 int len = strlen(str);
2082 if (len < 900) return;
2083 if (len > 998) str[998] = 0;
2085 char *cptr = strrchr(str, ',');
2088 char *lptr = strchr(cptr, '<');
2089 char *rptr = strchr(cptr, '>');
2091 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2097 void OutputCtdlMsgHeaders(
2098 struct CtdlMessage *TheMessage,
2099 int do_proto) /* do Citadel protocol responses? */
2104 char display_name[256];
2106 /* begin header processing loop for Citadel message format */
2107 safestrncpy(display_name, "<unknown>", sizeof display_name);
2108 if (!CM_IsEmpty(TheMessage, eAuthor)) {
2109 strcpy(buf, TheMessage->cm_fields[eAuthor]);
2110 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2111 safestrncpy(display_name, "****", sizeof display_name);
2113 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2114 safestrncpy(display_name, "anonymous", sizeof display_name);
2117 safestrncpy(display_name, buf, sizeof display_name);
2119 if ((is_room_aide())
2120 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2121 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2122 size_t tmp = strlen(display_name);
2123 snprintf(&display_name[tmp],
2124 sizeof display_name - tmp,
2129 /* Don't show Internet address for users on the
2130 * local Citadel network.
2133 if (!CM_IsEmpty(TheMessage, eNodeName) &&
2134 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
2139 /* Now spew the header fields in the order we like them. */
2140 for (i=0; i< NDiskFields; ++i) {
2142 Field = FieldOrder[i];
2143 if (Field != eMesageText) {
2144 if ( (!CM_IsEmpty(TheMessage, Field))
2145 && (msgkeys[Field] != NULL) ) {
2146 if ((Field == eenVelopeTo) ||
2147 (Field == eRecipient) ||
2148 (Field == eCarbonCopY)) {
2149 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
2151 if (Field == eAuthor) {
2152 if (do_proto) cprintf("%s=%s\n",
2156 else if ((Field == erFc822Addr) && (suppress_f)) {
2159 /* Masquerade display name if needed */
2161 if (do_proto) cprintf("%s=%s\n",
2163 TheMessage->cm_fields[Field]
2172 void OutputRFC822MsgHeaders(
2173 struct CtdlMessage *TheMessage,
2174 int flags, /* should the bessage be exported clean */
2176 char *mid, long sizeof_mid,
2177 char *suser, long sizeof_suser,
2178 char *luser, long sizeof_luser,
2179 char *fuser, long sizeof_fuser,
2180 char *snode, long sizeof_snode)
2182 char datestamp[100];
2183 int subject_found = 0;
2190 for (i = 0; i < 256; ++i) {
2191 if (TheMessage->cm_fields[i]) {
2192 mptr = mpptr = TheMessage->cm_fields[i];
2195 safestrncpy(luser, mptr, sizeof_luser);
2196 safestrncpy(suser, mptr, sizeof_suser);
2198 else if (i == 'Y') {
2199 if ((flags & QP_EADDR) != 0) {
2200 mptr = qp_encode_email_addrs(mptr);
2202 sanitize_truncated_recipient(mptr);
2203 cprintf("CC: %s%s", mptr, nl);
2205 else if (i == 'P') {
2206 cprintf("Return-Path: %s%s", mptr, nl);
2208 else if (i == eListID) {
2209 cprintf("List-ID: %s%s", mptr, nl);
2211 else if (i == 'V') {
2212 if ((flags & QP_EADDR) != 0)
2213 mptr = qp_encode_email_addrs(mptr);
2215 while ((*hptr != '\0') && isspace(*hptr))
2217 if (!IsEmptyStr(hptr))
2218 cprintf("Envelope-To: %s%s", hptr, nl);
2220 else if (i == 'U') {
2221 cprintf("Subject: %s%s", mptr, nl);
2225 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2226 else if (i == erFc822Addr)
2227 safestrncpy(fuser, mptr, sizeof_fuser);
2228 /* else if (i == 'O')
2229 cprintf("X-Citadel-Room: %s%s",
2232 safestrncpy(snode, mptr, sizeof_snode);
2235 if (haschar(mptr, '@') == 0)
2237 sanitize_truncated_recipient(mptr);
2238 cprintf("To: %s@%s", mptr, config.c_fqdn);
2243 if ((flags & QP_EADDR) != 0) {
2244 mptr = qp_encode_email_addrs(mptr);
2246 sanitize_truncated_recipient(mptr);
2247 cprintf("To: %s", mptr);
2251 else if (i == 'T') {
2252 datestring(datestamp, sizeof datestamp,
2253 atol(mptr), DATESTRING_RFC822);
2254 cprintf("Date: %s%s", datestamp, nl);
2256 else if (i == 'W') {
2257 cprintf("References: ");
2258 k = num_tokens(mptr, '|');
2259 for (j=0; j<k; ++j) {
2260 extract_token(buf, mptr, j, '|', sizeof buf);
2261 cprintf("<%s>", buf);
2270 else if (i == eReplyTo) {
2272 while ((*hptr != '\0') && isspace(*hptr))
2274 if (!IsEmptyStr(hptr))
2275 cprintf("Reply-To: %s%s", mptr, nl);
2281 if (subject_found == 0) {
2282 cprintf("Subject: (no subject)%s", nl);
2287 void Dump_RFC822HeadersBody(
2288 struct CtdlMessage *TheMessage,
2289 int headers_only, /* eschew the message body? */
2290 int flags, /* should the bessage be exported clean? */
2294 cit_uint8_t prev_ch;
2296 const char *StartOfText = StrBufNOTNULL;
2299 int nllen = strlen(nl);
2302 mptr = TheMessage->cm_fields[eMesageText];
2306 while (*mptr != '\0') {
2307 if (*mptr == '\r') {
2314 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2316 eoh = *(mptr+1) == '\n';
2320 StartOfText = strchr(StartOfText, '\n');
2321 StartOfText = strchr(StartOfText, '\n');
2324 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2325 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2326 ((headers_only != HEADERS_NONE) &&
2327 (headers_only != HEADERS_ONLY))
2329 if (*mptr == '\n') {
2330 memcpy(&outbuf[outlen], nl, nllen);
2332 outbuf[outlen] = '\0';
2335 outbuf[outlen++] = *mptr;
2339 if (flags & ESC_DOT)
2341 if ((prev_ch == '\n') &&
2343 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2345 outbuf[outlen++] = '.';
2350 if (outlen > 1000) {
2351 if (client_write(outbuf, outlen) == -1)
2353 struct CitContext *CCC = CC;
2354 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2361 client_write(outbuf, outlen);
2367 /* If the format type on disk is 1 (fixed-format), then we want
2368 * everything to be output completely literally ... regardless of
2369 * what message transfer format is in use.
2371 void DumpFormatFixed(
2372 struct CtdlMessage *TheMessage,
2373 int mode, /* how would you like that message? */
2380 int nllen = strlen (nl);
2383 mptr = TheMessage->cm_fields[eMesageText];
2385 if (mode == MT_MIME) {
2386 cprintf("Content-type: text/plain\n\n");
2390 while (ch = *mptr++, ch > 0) {
2394 if ((buflen > 250) && (!xlline)){
2398 while ((buflen > 0) &&
2399 (!isspace(buf[buflen])))
2405 mptr -= tbuflen - buflen;
2410 /* if we reach the outer bounds of our buffer,
2411 abort without respect what whe purge. */
2414 (buflen > SIZ - nllen - 2)))
2418 memcpy (&buf[buflen], nl, nllen);
2422 if (client_write(buf, buflen) == -1)
2424 struct CitContext *CCC = CC;
2425 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2437 if (!IsEmptyStr(buf))
2438 cprintf("%s%s", buf, nl);
2442 * Get a message off disk. (returns om_* values found in msgbase.h)
2444 int CtdlOutputPreLoadedMsg(
2445 struct CtdlMessage *TheMessage,
2446 int mode, /* how would you like that message? */
2447 int headers_only, /* eschew the message body? */
2448 int do_proto, /* do Citadel protocol responses? */
2449 int crlf, /* Use CRLF newlines instead of LF? */
2450 int flags /* should the bessage be exported clean? */
2452 struct CitContext *CCC = CC;
2455 const char *nl; /* newline string */
2458 /* Buffers needed for RFC822 translation. These are all filled
2459 * using functions that are bounds-checked, and therefore we can
2460 * make them substantially smaller than SIZ.
2468 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2469 ((TheMessage == NULL) ? "NULL" : "not null"),
2470 mode, headers_only, do_proto, crlf);
2472 strcpy(mid, "unknown");
2473 nl = (crlf ? "\r\n" : "\n");
2475 if (!CM_IsValidMsg(TheMessage)) {
2476 MSGM_syslog(LOG_ERR,
2477 "ERROR: invalid preloaded message for output\n");
2479 return(om_no_such_msg);
2482 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2483 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2485 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2486 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2489 /* Are we downloading a MIME component? */
2490 if (mode == MT_DOWNLOAD) {
2491 if (TheMessage->cm_format_type != FMT_RFC822) {
2493 cprintf("%d This is not a MIME message.\n",
2494 ERROR + ILLEGAL_VALUE);
2495 } else if (CCC->download_fp != NULL) {
2496 if (do_proto) cprintf(
2497 "%d You already have a download open.\n",
2498 ERROR + RESOURCE_BUSY);
2500 /* Parse the message text component */
2501 mptr = TheMessage->cm_fields[eMesageText];
2502 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2503 /* If there's no file open by this time, the requested
2504 * section wasn't found, so print an error
2506 if (CCC->download_fp == NULL) {
2507 if (do_proto) cprintf(
2508 "%d Section %s not found.\n",
2509 ERROR + FILE_NOT_FOUND,
2510 CCC->download_desired_section);
2513 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2516 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2517 * in a single server operation instead of opening a download file.
2519 if (mode == MT_SPEW_SECTION) {
2520 if (TheMessage->cm_format_type != FMT_RFC822) {
2522 cprintf("%d This is not a MIME message.\n",
2523 ERROR + ILLEGAL_VALUE);
2525 /* Parse the message text component */
2528 mptr = TheMessage->cm_fields[eMesageText];
2529 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2530 /* If section wasn't found, print an error
2533 if (do_proto) cprintf(
2534 "%d Section %s not found.\n",
2535 ERROR + FILE_NOT_FOUND,
2536 CCC->download_desired_section);
2539 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2542 /* now for the user-mode message reading loops */
2543 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2545 /* Does the caller want to skip the headers? */
2546 if (headers_only == HEADERS_NONE) goto START_TEXT;
2548 /* Tell the client which format type we're using. */
2549 if ( (mode == MT_CITADEL) && (do_proto) ) {
2550 cprintf("type=%d\n", TheMessage->cm_format_type);
2553 /* nhdr=yes means that we're only displaying headers, no body */
2554 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2555 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2558 cprintf("nhdr=yes\n");
2561 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2562 OutputCtdlMsgHeaders(TheMessage, do_proto);
2565 /* begin header processing loop for RFC822 transfer format */
2569 strcpy(snode, NODENAME);
2570 if (mode == MT_RFC822)
2571 OutputRFC822MsgHeaders(
2576 suser, sizeof(suser),
2577 luser, sizeof(luser),
2578 fuser, sizeof(fuser),
2579 snode, sizeof(snode)
2583 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2584 suser[i] = tolower(suser[i]);
2585 if (!isalnum(suser[i])) suser[i]='_';
2588 if (mode == MT_RFC822) {
2589 if (!strcasecmp(snode, NODENAME)) {
2590 safestrncpy(snode, FQDN, sizeof snode);
2593 /* Construct a fun message id */
2594 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2595 if (strchr(mid, '@')==NULL) {
2596 cprintf("@%s", snode);
2600 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2601 cprintf("From: \"----\" <x@x.org>%s", nl);
2603 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2604 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2606 else if (!IsEmptyStr(fuser)) {
2607 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2610 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2613 /* Blank line signifying RFC822 end-of-headers */
2614 if (TheMessage->cm_format_type != FMT_RFC822) {
2619 /* end header processing loop ... at this point, we're in the text */
2621 if (headers_only == HEADERS_FAST) goto DONE;
2623 /* Tell the client about the MIME parts in this message */
2624 if (TheMessage->cm_format_type == FMT_RFC822) {
2625 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2626 mptr = TheMessage->cm_fields[eMesageText];
2627 memset(&ma, 0, sizeof(struct ma_info));
2628 mime_parser(mptr, NULL,
2629 (do_proto ? *list_this_part : NULL),
2630 (do_proto ? *list_this_pref : NULL),
2631 (do_proto ? *list_this_suff : NULL),
2634 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2635 Dump_RFC822HeadersBody(
2644 if (headers_only == HEADERS_ONLY) {
2648 /* signify start of msg text */
2649 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2650 if (do_proto) cprintf("text\n");
2653 if (TheMessage->cm_format_type == FMT_FIXED)
2656 mode, /* how would you like that message? */
2659 /* If the message on disk is format 0 (Citadel vari-format), we
2660 * output using the formatter at 80 columns. This is the final output
2661 * form if the transfer format is RFC822, but if the transfer format
2662 * is Citadel proprietary, it'll still work, because the indentation
2663 * for new paragraphs is correct and the client will reformat the
2664 * message to the reader's screen width.
2666 if (TheMessage->cm_format_type == FMT_CITADEL) {
2667 mptr = TheMessage->cm_fields[eMesageText];
2669 if (mode == MT_MIME) {
2670 cprintf("Content-type: text/x-citadel-variformat\n\n");
2675 /* If the message on disk is format 4 (MIME), we've gotta hand it
2676 * off to the MIME parser. The client has already been told that
2677 * this message is format 1 (fixed format), so the callback function
2678 * we use will display those parts as-is.
2680 if (TheMessage->cm_format_type == FMT_RFC822) {
2681 memset(&ma, 0, sizeof(struct ma_info));
2683 if (mode == MT_MIME) {
2684 ma.use_fo_hooks = 0;
2685 strcpy(ma.chosen_part, "1");
2686 ma.chosen_pref = 9999;
2687 ma.dont_decode = CCC->msg4_dont_decode;
2688 mime_parser(mptr, NULL,
2689 *choose_preferred, *fixed_output_pre,
2690 *fixed_output_post, (void *)&ma, 1);
2691 mime_parser(mptr, NULL,
2692 *output_preferred, NULL, NULL, (void *)&ma, 1);
2695 ma.use_fo_hooks = 1;
2696 mime_parser(mptr, NULL,
2697 *fixed_output, *fixed_output_pre,
2698 *fixed_output_post, (void *)&ma, 0);
2703 DONE: /* now we're done */
2704 if (do_proto) cprintf("000\n");
2710 * display a message (mode 0 - Citadel proprietary)
2712 void cmd_msg0(char *cmdbuf)
2715 int headers_only = HEADERS_ALL;
2717 msgid = extract_long(cmdbuf, 0);
2718 headers_only = extract_int(cmdbuf, 1);
2720 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2726 * display a message (mode 2 - RFC822)
2728 void cmd_msg2(char *cmdbuf)
2731 int headers_only = HEADERS_ALL;
2733 msgid = extract_long(cmdbuf, 0);
2734 headers_only = extract_int(cmdbuf, 1);
2736 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2742 * display a message (mode 3 - IGnet raw format - internal programs only)
2744 void cmd_msg3(char *cmdbuf)
2747 struct CtdlMessage *msg = NULL;
2750 if (CC->internal_pgm == 0) {
2751 cprintf("%d This command is for internal programs only.\n",
2752 ERROR + HIGHER_ACCESS_REQUIRED);
2756 msgnum = extract_long(cmdbuf, 0);
2757 msg = CtdlFetchMessage(msgnum, 1);
2759 cprintf("%d Message %ld not found.\n",
2760 ERROR + MESSAGE_NOT_FOUND, msgnum);
2764 serialize_message(&smr, msg);
2768 cprintf("%d Unable to serialize message\n",
2769 ERROR + INTERNAL_ERROR);
2773 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2774 client_write((char *)smr.ser, (int)smr.len);
2781 * Display a message using MIME content types
2783 void cmd_msg4(char *cmdbuf)
2788 msgid = extract_long(cmdbuf, 0);
2789 extract_token(section, cmdbuf, 1, '|', sizeof section);
2790 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2796 * Client tells us its preferred message format(s)
2798 void cmd_msgp(char *cmdbuf)
2800 if (!strcasecmp(cmdbuf, "dont_decode")) {
2801 CC->msg4_dont_decode = 1;
2802 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2805 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2806 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2812 * Open a component of a MIME message as a download file
2814 void cmd_opna(char *cmdbuf)
2817 char desired_section[128];
2819 msgid = extract_long(cmdbuf, 0);
2820 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2821 safestrncpy(CC->download_desired_section, desired_section,
2822 sizeof CC->download_desired_section);
2823 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2828 * Open a component of a MIME message and transmit it all at once
2830 void cmd_dlat(char *cmdbuf)
2833 char desired_section[128];
2835 msgid = extract_long(cmdbuf, 0);
2836 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2837 safestrncpy(CC->download_desired_section, desired_section,
2838 sizeof CC->download_desired_section);
2839 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2844 * Save one or more message pointers into a specified room
2845 * (Returns 0 for success, nonzero for failure)
2846 * roomname may be NULL to use the current room
2848 * Note that the 'supplied_msg' field may be set to NULL, in which case
2849 * the message will be fetched from disk, by number, if we need to perform
2850 * replication checks. This adds an additional database read, so if the
2851 * caller already has the message in memory then it should be supplied. (Obviously
2852 * this mode of operation only works if we're saving a single message.)
2854 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2855 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2857 struct CitContext *CCC = CC;
2859 char hold_rm[ROOMNAMELEN];
2860 struct cdbdata *cdbfr;
2863 long highest_msg = 0L;
2866 struct CtdlMessage *msg = NULL;
2868 long *msgs_to_be_merged = NULL;
2869 int num_msgs_to_be_merged = 0;
2871 MSG_syslog(LOG_DEBUG,
2872 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2873 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2876 strcpy(hold_rm, CCC->room.QRname);
2879 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2880 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2881 if (num_newmsgs > 1) supplied_msg = NULL;
2883 /* Now the regular stuff */
2884 if (CtdlGetRoomLock(&CCC->room,
2885 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2887 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2888 return(ERROR + ROOM_NOT_FOUND);
2892 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2893 num_msgs_to_be_merged = 0;
2896 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2897 if (cdbfr == NULL) {
2901 msglist = (long *) cdbfr->ptr;
2902 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2903 num_msgs = cdbfr->len / sizeof(long);
2908 /* Create a list of msgid's which were supplied by the caller, but do
2909 * not already exist in the target room. It is absolutely taboo to
2910 * have more than one reference to the same message in a room.
2912 for (i=0; i<num_newmsgs; ++i) {
2914 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2915 if (msglist[j] == newmsgidlist[i]) {
2920 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2924 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2927 * Now merge the new messages
2929 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2930 if (msglist == NULL) {
2931 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2932 free(msgs_to_be_merged);
2933 return (ERROR + INTERNAL_ERROR);
2935 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2936 num_msgs += num_msgs_to_be_merged;
2938 /* Sort the message list, so all the msgid's are in order */
2939 num_msgs = sort_msglist(msglist, num_msgs);
2941 /* Determine the highest message number */
2942 highest_msg = msglist[num_msgs - 1];
2944 /* Write it back to disk. */
2945 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2946 msglist, (int)(num_msgs * sizeof(long)));
2948 /* Free up the memory we used. */
2951 /* Update the highest-message pointer and unlock the room. */
2952 CCC->room.QRhighest = highest_msg;
2953 CtdlPutRoomLock(&CCC->room);
2955 /* Perform replication checks if necessary */
2956 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2957 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2959 for (i=0; i<num_msgs_to_be_merged; ++i) {
2960 msgid = msgs_to_be_merged[i];
2962 if (supplied_msg != NULL) {
2966 msg = CtdlFetchMessage(msgid, 0);
2970 ReplicationChecks(msg);
2972 /* If the message has an Exclusive ID, index that... */
2973 if (!CM_IsEmpty(msg, eExclusiveID)) {
2974 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
2977 /* Free up the memory we may have allocated */
2978 if (msg != supplied_msg) {
2987 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2990 /* Submit this room for processing by hooks */
2991 PerformRoomHooks(&CCC->room);
2993 /* Go back to the room we were in before we wandered here... */
2994 CtdlGetRoom(&CCC->room, hold_rm);
2996 /* Bump the reference count for all messages which were merged */
2997 if (!suppress_refcount_adj) {
2998 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
3001 /* Free up memory... */
3002 if (msgs_to_be_merged != NULL) {
3003 free(msgs_to_be_merged);
3006 /* Return success. */
3012 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
3015 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
3016 int do_repl_check, struct CtdlMessage *supplied_msg)
3018 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
3025 * Message base operation to save a new message to the message store
3026 * (returns new message number)
3028 * This is the back end for CtdlSubmitMsg() and should not be directly
3029 * called by server-side modules.
3032 long send_message(struct CtdlMessage *msg) {
3033 struct CitContext *CCC = CC;
3042 /* Get a new message number */
3043 newmsgid = get_new_message_number();
3044 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
3045 (long unsigned int) time(NULL),
3046 (long unsigned int) newmsgid,
3050 /* Generate an ID if we don't have one already */
3051 if (CM_IsEmpty(msg, emessageId)) {
3052 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
3055 /* If the message is big, set its body aside for storage elsewhere */
3056 if (!CM_IsEmpty(msg, eMesageText)) {
3057 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
3059 holdM = msg->cm_fields[eMesageText];
3060 msg->cm_fields[eMesageText] = NULL;
3064 /* Serialize our data structure for storage in the database */
3065 serialize_message(&smr, msg);
3068 msg->cm_fields[eMesageText] = holdM;
3072 cprintf("%d Unable to serialize message\n",
3073 ERROR + INTERNAL_ERROR);
3077 /* Write our little bundle of joy into the message base */
3078 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3079 smr.ser, smr.len) < 0) {
3080 MSGM_syslog(LOG_ERR, "Can't store message\n");
3084 cdb_store(CDB_BIGMSGS,
3094 /* Free the memory we used for the serialized message */
3097 /* Return the *local* message ID to the caller
3098 * (even if we're storing an incoming network message)
3106 * Serialize a struct CtdlMessage into the format used on disk and network.
3108 * This function loads up a "struct ser_ret" (defined in server.h) which
3109 * contains the length of the serialized message and a pointer to the
3110 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3112 void serialize_message(struct ser_ret *ret, /* return values */
3113 struct CtdlMessage *msg) /* unserialized msg */
3115 struct CitContext *CCC = CC;
3116 size_t wlen, fieldlen;
3118 long lengths[NDiskFields];
3120 memset(lengths, 0, sizeof(lengths));
3123 * Check for valid message format
3125 if (CM_IsValidMsg(msg) == 0) {
3126 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3133 for (i=0; i < NDiskFields; ++i)
3134 if (msg->cm_fields[FieldOrder[i]] != NULL)
3136 lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
3137 ret->len += lengths[i] + 2;
3140 ret->ser = malloc(ret->len);
3141 if (ret->ser == NULL) {
3142 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3143 (long)ret->len, strerror(errno));
3150 ret->ser[1] = msg->cm_anon_type;
3151 ret->ser[2] = msg->cm_format_type;
3154 for (i=0; i < NDiskFields; ++i)
3155 if (msg->cm_fields[FieldOrder[i]] != NULL)
3157 fieldlen = lengths[i];
3158 ret->ser[wlen++] = (char)FieldOrder[i];
3160 memcpy(&ret->ser[wlen],
3161 msg->cm_fields[FieldOrder[i]],
3164 wlen = wlen + fieldlen + 1;
3167 if (ret->len != wlen) {
3168 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3169 (long)ret->len, (long)wlen);
3177 * Check to see if any messages already exist in the current room which
3178 * carry the same Exclusive ID as this one. If any are found, delete them.
3180 void ReplicationChecks(struct CtdlMessage *msg) {
3181 struct CitContext *CCC = CC;
3182 long old_msgnum = (-1L);
3184 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3186 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3189 /* No exclusive id? Don't do anything. */
3190 if (msg == NULL) return;
3191 if (CM_IsEmpty(msg, eExclusiveID)) return;
3193 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3194 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
3196 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
3197 if (old_msgnum > 0L) {
3198 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3199 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3206 * Save a message to disk and submit it into the delivery system.
3208 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3209 struct recptypes *recps, /* recipients (if mail) */
3210 const char *force, /* force a particular room? */
3211 int flags /* should the message be exported clean? */
3214 char submit_filename[128];
3215 char hold_rm[ROOMNAMELEN];
3216 char actual_rm[ROOMNAMELEN];
3217 char force_room[ROOMNAMELEN];
3218 char content_type[SIZ]; /* We have to learn this */
3219 char recipient[SIZ];
3222 const char *mptr = NULL;
3223 struct ctdluser userbuf;
3225 struct MetaData smi;
3226 FILE *network_fp = NULL;
3227 static int seqnum = 1;
3228 struct CtdlMessage *imsg = NULL;
3230 size_t instr_alloc = 0;
3232 char *hold_R, *hold_D;
3233 char *collected_addresses = NULL;
3234 struct addresses_to_be_filed *aptr = NULL;
3235 StrBuf *saved_rfc822_version = NULL;
3236 int qualified_for_journaling = 0;
3237 CitContext *CCC = MyContext();
3238 char bounce_to[1024] = "";
3241 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3242 if (CM_IsValidMsg(msg) == 0) return(-1); /* self check */
3244 /* If this message has no timestamp, we take the liberty of
3245 * giving it one, right now.
3247 if (CM_IsEmpty(msg, eTimestamp)) {
3248 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
3251 /* If this message has no path, we generate one.
3253 if (CM_IsEmpty(msg, eMessagePath)) {
3254 if (!CM_IsEmpty(msg, eAuthor)) {
3255 CM_CopyField(msg, eMessagePath, eAuthor);
3256 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
3257 if (isspace(msg->cm_fields[eMessagePath][a])) {
3258 msg->cm_fields[eMessagePath][a] = ' ';
3263 CM_SetField(msg, eMessagePath, HKEY("unknown"));
3267 if (force == NULL) {
3268 force_room[0] = '\0';
3271 strcpy(force_room, force);
3274 /* Learn about what's inside, because it's what's inside that counts */
3275 if (CM_IsEmpty(msg, eMesageText)) {
3276 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3280 switch (msg->cm_format_type) {
3282 strcpy(content_type, "text/x-citadel-variformat");
3285 strcpy(content_type, "text/plain");
3288 strcpy(content_type, "text/plain");
3289 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3292 safestrncpy(content_type, &mptr[13], sizeof content_type);
3293 striplt(content_type);
3294 aptr = content_type;
3295 while (!IsEmptyStr(aptr)) {
3307 /* Goto the correct room */
3308 room = (recps) ? CCC->room.QRname : SENTITEMS;
3309 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3310 strcpy(hold_rm, CCC->room.QRname);
3311 strcpy(actual_rm, CCC->room.QRname);
3312 if (recps != NULL) {
3313 strcpy(actual_rm, SENTITEMS);
3316 /* If the user is a twit, move to the twit room for posting */
3318 if (CCC->user.axlevel == AxProbU) {
3319 strcpy(hold_rm, actual_rm);
3320 strcpy(actual_rm, config.c_twitroom);
3321 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3325 /* ...or if this message is destined for Aide> then go there. */
3326 if (!IsEmptyStr(force_room)) {
3327 strcpy(actual_rm, force_room);
3330 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3331 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3332 /* CtdlGetRoom(&CCC->room, actual_rm); */
3333 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3337 * If this message has no O (room) field, generate one.
3339 if (CM_IsEmpty(msg, eOriginalRoom)) {
3340 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3343 /* Perform "before save" hooks (aborting if any return nonzero) */
3344 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3345 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3348 * If this message has an Exclusive ID, and the room is replication
3349 * checking enabled, then do replication checks.
3351 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3352 ReplicationChecks(msg);
3355 /* Save it to disk */
3356 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3357 newmsgid = send_message(msg);
3358 if (newmsgid <= 0L) return(-5);
3360 /* Write a supplemental message info record. This doesn't have to
3361 * be a critical section because nobody else knows about this message
3364 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3365 memset(&smi, 0, sizeof(struct MetaData));
3366 smi.meta_msgnum = newmsgid;
3367 smi.meta_refcount = 0;
3368 safestrncpy(smi.meta_content_type, content_type,
3369 sizeof smi.meta_content_type);
3372 * Measure how big this message will be when rendered as RFC822.
3373 * We do this for two reasons:
3374 * 1. We need the RFC822 length for the new metadata record, so the
3375 * POP and IMAP services don't have to calculate message lengths
3376 * while the user is waiting (multiplied by potentially hundreds
3377 * or thousands of messages).
3378 * 2. If journaling is enabled, we will need an RFC822 version of the
3379 * message to attach to the journalized copy.
3381 if (CCC->redirect_buffer != NULL) {
3382 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3385 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3386 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3387 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3388 saved_rfc822_version = CCC->redirect_buffer;
3389 CCC->redirect_buffer = NULL;
3393 /* Now figure out where to store the pointers */
3394 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3396 /* If this is being done by the networker delivering a private
3397 * message, we want to BYPASS saving the sender's copy (because there
3398 * is no local sender; it would otherwise go to the Trashcan).
3400 if ((!CCC->internal_pgm) || (recps == NULL)) {
3401 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3402 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3403 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3407 /* For internet mail, drop a copy in the outbound queue room */
3408 if ((recps != NULL) && (recps->num_internet > 0)) {
3409 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3412 /* If other rooms are specified, drop them there too. */
3413 if ((recps != NULL) && (recps->num_room > 0))
3414 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3415 extract_token(recipient, recps->recp_room, i,
3416 '|', sizeof recipient);
3417 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3418 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3421 /* Bump this user's messages posted counter. */
3422 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3423 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3424 CCC->user.posted = CCC->user.posted + 1;
3425 CtdlPutUserLock(&CCC->user);
3427 /* Decide where bounces need to be delivered */
3428 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3429 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3431 else if (CCC->logged_in) {
3432 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3435 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3438 /* If this is private, local mail, make a copy in the
3439 * recipient's mailbox and bump the reference count.
3441 if ((recps != NULL) && (recps->num_local > 0))
3442 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3444 recipientlen = extract_token(recipient,
3445 recps->recp_local, i,
3446 '|', sizeof recipient);
3447 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3449 if (CtdlGetUser(&userbuf, recipient) == 0) {
3450 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3451 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3452 CtdlBumpNewMailCounter(userbuf.usernum);
3453 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3454 /* Generate a instruction message for the Funambol notification
3455 * server, in the same style as the SMTP queue
3459 instr = malloc(instr_alloc);
3460 instrlen = snprintf(
3462 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3466 (long)time(NULL), //todo: time() is expensive!
3470 imsg = malloc(sizeof(struct CtdlMessage));
3471 memset(imsg, 0, sizeof(struct CtdlMessage));
3472 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3473 imsg->cm_anon_type = MES_NORMAL;
3474 imsg->cm_format_type = FMT_RFC822;
3475 CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
3476 CM_SetField(imsg, eAuthor, HKEY("Citadel"));
3477 CM_SetField(imsg, eJournal, HKEY("do not journal"));
3478 CM_SetAsField(imsg, eMesageText, &instr, instrlen);
3479 CM_SetField(imsg, eExtnotify, recipient, recipientlen);
3480 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3485 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3486 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3490 /* Perform "after save" hooks */
3491 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3493 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
3494 PerformMessageHooks(msg, EVT_AFTERSAVE);
3495 CM_FlushField(msg, eVltMsgNum);
3497 /* For IGnet mail, we have to save a new copy into the spooler for
3498 * each recipient, with the R and D fields set to the recipient and
3499 * destination-node. This has two ugly side effects: all other
3500 * recipients end up being unlisted in this recipient's copy of the
3501 * message, and it has to deliver multiple messages to the same
3502 * node. We'll revisit this again in a year or so when everyone has
3503 * a network spool receiver that can handle the new style messages.
3505 if ((recps != NULL) && (recps->num_ignet > 0))
3506 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3507 extract_token(recipient, recps->recp_ignet, i,
3508 '|', sizeof recipient);
3510 hold_R = msg->cm_fields[eRecipient];
3511 hold_D = msg->cm_fields[eDestination];
3512 msg->cm_fields[eRecipient] = malloc(SIZ);
3513 msg->cm_fields[eDestination] = malloc(128);
3514 extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3515 extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3517 serialize_message(&smr, msg);
3519 snprintf(submit_filename, sizeof submit_filename,
3520 "%s/netmail.%04lx.%04x.%04x",
3522 (long) getpid(), CCC->cs_pid, ++seqnum);
3523 network_fp = fopen(submit_filename, "wb+");
3524 if (network_fp != NULL) {
3525 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3527 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3535 free(msg->cm_fields[eRecipient]);
3536 free(msg->cm_fields[eDestination]);
3537 msg->cm_fields[eRecipient] = hold_R;
3538 msg->cm_fields[eDestination] = hold_D;
3541 /* Go back to the room we started from */
3542 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3543 if (strcasecmp(hold_rm, CCC->room.QRname))
3544 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3546 /* For internet mail, generate delivery instructions.
3547 * Yes, this is recursive. Deal with it. Infinite recursion does
3548 * not happen because the delivery instructions message does not
3549 * contain a recipient.
3551 if ((recps != NULL) && (recps->num_internet > 0)) {
3552 StrBuf *SpoolMsg = NewStrBuf();
3555 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3557 StrBufPrintf(SpoolMsg,
3558 "Content-type: "SPOOLMIME"\n"
3567 if (recps->envelope_from != NULL) {
3568 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3569 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3570 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3572 if (recps->sending_room != NULL) {
3573 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3574 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3575 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3578 nTokens = num_tokens(recps->recp_internet, '|');
3579 for (i = 0; i < nTokens; i++) {
3581 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3583 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3584 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3585 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3589 imsg = malloc(sizeof(struct CtdlMessage));
3590 memset(imsg, 0, sizeof(struct CtdlMessage));
3591 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3592 imsg->cm_anon_type = MES_NORMAL;
3593 imsg->cm_format_type = FMT_RFC822;
3594 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3595 imsg->cm_fields[eAuthor] = strdup("Citadel");
3596 imsg->cm_fields[eJournal] = strdup("do not journal");
3597 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3598 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3603 * Any addresses to harvest for someone's address book?
3605 if ( (CCC->logged_in) && (recps != NULL) ) {
3606 collected_addresses = harvest_collected_addresses(msg);
3609 if (collected_addresses != NULL) {
3610 aptr = (struct addresses_to_be_filed *)
3611 malloc(sizeof(struct addresses_to_be_filed));
3612 CtdlMailboxName(actual_rm, sizeof actual_rm,
3613 &CCC->user, USERCONTACTSROOM);
3614 aptr->roomname = strdup(actual_rm);
3615 aptr->collected_addresses = collected_addresses;
3616 begin_critical_section(S_ATBF);
3619 end_critical_section(S_ATBF);
3623 * Determine whether this message qualifies for journaling.
3625 if (!CM_IsEmpty(msg, eJournal)) {
3626 qualified_for_journaling = 0;
3629 if (recps == NULL) {
3630 qualified_for_journaling = config.c_journal_pubmsgs;
3632 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3633 qualified_for_journaling = config.c_journal_email;
3636 qualified_for_journaling = config.c_journal_pubmsgs;
3641 * Do we have to perform journaling? If so, hand off the saved
3642 * RFC822 version will be handed off to the journaler for background
3643 * submit. Otherwise, we have to free the memory ourselves.
3645 if (saved_rfc822_version != NULL) {
3646 if (qualified_for_journaling) {
3647 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3650 FreeStrBuf(&saved_rfc822_version);
3660 * Convenience function for generating small administrative messages.
3662 void quickie_message(const char *from,
3663 const char *fromaddr,
3668 const char *subject)
3670 struct CtdlMessage *msg;
3671 struct recptypes *recp = NULL;
3673 msg = malloc(sizeof(struct CtdlMessage));
3674 memset(msg, 0, sizeof(struct CtdlMessage));
3675 msg->cm_magic = CTDLMESSAGE_MAGIC;
3676 msg->cm_anon_type = MES_NORMAL;
3677 msg->cm_format_type = format_type;
3680 msg->cm_fields[eAuthor] = strdup(from);
3682 else if (fromaddr != NULL) {
3683 msg->cm_fields[eAuthor] = strdup(fromaddr);
3684 if (strchr(msg->cm_fields[eAuthor], '@')) {
3685 *strchr(msg->cm_fields[eAuthor], '@') = 0;
3689 msg->cm_fields[eAuthor] = strdup("Citadel");
3692 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3693 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3694 msg->cm_fields[eNodeName] = strdup(NODENAME);
3696 msg->cm_fields[eRecipient] = strdup(to);
3697 recp = validate_recipients(to, NULL, 0);
3699 if (subject != NULL) {
3700 msg->cm_fields[eMsgSubject] = strdup(subject);
3702 msg->cm_fields[eMesageText] = strdup(text);
3704 CtdlSubmitMsg(msg, recp, room, 0);
3706 if (recp != NULL) free_recipients(recp);
3709 void flood_protect_quickie_message(const char *from,
3710 const char *fromaddr,
3715 const char *subject,
3717 const char **CritStr,
3724 u_char rawdigest[MD5_DIGEST_LEN];
3725 struct MD5Context md5context;
3729 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3731 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3732 MD5Init(&md5context);
3734 for (i = 0; i < nCriterions; i++)
3735 MD5Update(&md5context,
3736 (const unsigned char*)CritStr[i], CritStrLen[i]);
3737 MD5Update(&md5context,
3738 (const unsigned char*)timestamp, tslen);
3739 MD5Final(rawdigest, &md5context);
3741 guid = NewStrBufPlain(NULL,
3742 MD5_DIGEST_LEN * 2 + 12);
3743 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3744 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3745 if (StrLength(guid) > 40)
3746 StrBufCutAt(guid, 40, NULL);
3748 if (CheckIfAlreadySeen("FPAideMessage",
3757 /* yes, we did. flood protection kicks in. */
3759 "not sending message again\n");
3763 /* no, this message isn't sent recently; go ahead. */
3764 quickie_message(from,
3775 * Back end function used by CtdlMakeMessage() and similar functions
3777 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3779 size_t maxlen, /* maximum message length */
3780 StrBuf *exist, /* if non-null, append to it;
3781 exist is ALWAYS freed */
3782 int crlf, /* CRLF newlines instead of LF */
3783 int *sock /* socket handle or 0 for this session's client socket */
3792 LineBuf = NewStrBufPlain(NULL, SIZ);
3793 if (exist == NULL) {
3794 Message = NewStrBufPlain(NULL, 4 * SIZ);
3797 Message = NewStrBufDup(exist);
3800 /* Do we need to change leading ".." to "." for SMTP escaping? */
3801 if ((tlen == 1) && (*terminator == '.')) {
3805 /* read in the lines of message text one by one */
3808 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3813 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3815 if ((StrLength(LineBuf) == tlen) &&
3816 (!strcmp(ChrPtr(LineBuf), terminator)))
3819 if ( (!flushing) && (!finished) ) {
3821 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3824 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3827 /* Unescape SMTP-style input of two dots at the beginning of the line */
3829 (StrLength(LineBuf) == 2) &&
3830 (!strcmp(ChrPtr(LineBuf), "..")))
3832 StrBufCutLeft(LineBuf, 1);
3835 StrBufAppendBuf(Message, LineBuf, 0);
3838 /* if we've hit the max msg length, flush the rest */
3839 if (StrLength(Message) >= maxlen) flushing = 1;
3841 } while (!finished);
3842 FreeStrBuf(&LineBuf);
3846 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3850 FreeStrBuf(&(*Msg)->MsgBuf);
3856 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3858 size_t maxlen, /* maximum message length */
3859 size_t expectlen, /* if we expect a message, how long should it be? */
3860 StrBuf *exist, /* if non-null, append to it;
3861 exist is ALWAYS freed */
3862 long eLen, /* length of exist */
3863 int crlf /* CRLF newlines instead of LF */
3866 ReadAsyncMsg *NewMsg;
3868 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3869 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3871 if (exist == NULL) {
3874 if (expectlen == 0) {
3878 len = expectlen + 10;
3880 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3883 NewMsg->MsgBuf = NewStrBufDup(exist);
3885 /* Do we need to change leading ".." to "." for SMTP escaping? */
3886 if ((tlen == 1) && (*terminator == '.')) {
3890 NewMsg->terminator = terminator;
3891 NewMsg->tlen = tlen;
3893 NewMsg->maxlen = maxlen;
3895 NewMsg->crlf = crlf;
3901 * Back end function used by CtdlMakeMessage() and similar functions
3903 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3905 ReadAsyncMsg *ReadMsg;
3906 int MsgFinished = 0;
3907 eReadState Finished = eMustReadMore;
3912 const char *pch = ChrPtr(IO->SendBuf.Buf);
3913 const char *pchh = IO->SendBuf.ReadWritePointer;
3919 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3920 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3921 ((CitContext*)(IO->CitContext))->ServiceName,
3924 fd = fopen(fn, "a+");
3927 ReadMsg = IO->ReadMsg;
3929 /* read in the lines of message text one by one */
3931 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3934 case eMustReadMore: /// read new from socket...
3936 if (IO->RecvBuf.ReadWritePointer != NULL) {
3937 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3938 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3940 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3944 fprintf(fd, "BufferEmpty! \n");
3950 case eBufferNotEmpty: /* shouldn't happen... */
3951 case eReadSuccess: /// done for now...
3953 case eReadFail: /// WHUT?
3959 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3960 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3963 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3966 else if (!ReadMsg->flushing) {
3969 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3972 /* Unescape SMTP-style input of two dots at the beginning of the line */
3973 if ((ReadMsg->dodot) &&
3974 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3975 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3978 fprintf(fd, "UnEscaped!\n");
3980 StrBufCutLeft(IO->IOBuf, 1);
3983 if (ReadMsg->crlf) {
3984 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3987 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3990 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3993 /* if we've hit the max msg length, flush the rest */
3994 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3996 } while (!MsgFinished);
3999 fprintf(fd, "Done with reading; %s.\n, ",
4000 (MsgFinished)?"Message Finished": "FAILED");
4004 return eReadSuccess;
4011 * Back end function used by CtdlMakeMessage() and similar functions
4013 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
4015 size_t maxlen, /* maximum message length */
4016 StrBuf *exist, /* if non-null, append to it;
4017 exist is ALWAYS freed */
4018 int crlf, /* CRLF newlines instead of LF */
4019 int *sock /* socket handle or 0 for this session's client socket */
4024 Message = CtdlReadMessageBodyBuf(terminator,
4030 if (Message == NULL)
4033 return SmashStrBuf(&Message);
4038 * Build a binary message to be saved on disk.
4039 * (NOTE: if you supply 'preformatted_text', the buffer you give it
4040 * will become part of the message. This means you are no longer
4041 * responsible for managing that memory -- it will be freed along with
4042 * the rest of the fields when CM_Free() is called.)
4045 struct CtdlMessage *CtdlMakeMessage(
4046 struct ctdluser *author, /* author's user structure */
4047 char *recipient, /* NULL if it's not mail */
4048 char *recp_cc, /* NULL if it's not mail */
4049 char *room, /* room where it's going */
4050 int type, /* see MES_ types in header file */
4051 int format_type, /* variformat, plain text, MIME... */
4052 char *fake_name, /* who we're masquerading as */
4053 char *my_email, /* which of my email addresses to use (empty is ok) */
4054 char *subject, /* Subject (optional) */
4055 char *supplied_euid, /* ...or NULL if this is irrelevant */
4056 char *preformatted_text, /* ...or NULL to read text from client */
4057 char *references /* Thread references */
4059 char dest_node[256];
4061 struct CtdlMessage *msg;
4063 StrBuf *FakeEncAuthor = NULL;
4065 msg = malloc(sizeof(struct CtdlMessage));
4066 memset(msg, 0, sizeof(struct CtdlMessage));
4067 msg->cm_magic = CTDLMESSAGE_MAGIC;
4068 msg->cm_anon_type = type;
4069 msg->cm_format_type = format_type;
4071 /* Don't confuse the poor folks if it's not routed mail. */
4072 strcpy(dest_node, "");
4074 if (recipient != NULL) striplt(recipient);
4075 if (recp_cc != NULL) striplt(recp_cc);
4077 /* Path or Return-Path */
4078 if (my_email == NULL) my_email = "";
4080 if (!IsEmptyStr(my_email)) {
4081 msg->cm_fields[eMessagePath] = strdup(my_email);
4084 snprintf(buf, sizeof buf, "%s", author->fullname);
4085 msg->cm_fields[eMessagePath] = strdup(buf);
4087 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
4089 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
4090 msg->cm_fields[eTimestamp] = strdup(buf);
4092 if ((fake_name != NULL) && (fake_name[0])) { /* author */
4093 FakeAuthor = NewStrBufPlain (fake_name, -1);
4096 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4098 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4099 msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
4100 FreeStrBuf(&FakeAuthor);
4102 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4103 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
4106 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
4109 msg->cm_fields[eNodeName] = strdup(NODENAME); /* nodename */
4110 msg->cm_fields[eHumanNode] = strdup(HUMANNODE); /* hnodename */
4112 if ((recipient != NULL) && (recipient[0] != 0)) {
4113 msg->cm_fields[eRecipient] = strdup(recipient);
4115 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4116 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
4118 if (dest_node[0] != 0) {
4119 msg->cm_fields[eDestination] = strdup(dest_node);
4122 if (!IsEmptyStr(my_email)) {
4123 msg->cm_fields[erFc822Addr] = strdup(my_email);
4125 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4126 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
4129 if (subject != NULL) {
4132 length = strlen(subject);
4138 while ((subject[i] != '\0') &&
4139 (IsAscii = isascii(subject[i]) != 0 ))
4142 msg->cm_fields[eMsgSubject] = strdup(subject);
4143 else /* ok, we've got utf8 in the string. */
4145 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
4151 if (supplied_euid != NULL) {
4152 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
4155 if ((references != NULL) && (!IsEmptyStr(references))) {
4156 if (msg->cm_fields[eWeferences] != NULL)
4157 free(msg->cm_fields[eWeferences]);
4158 msg->cm_fields[eWeferences] = strdup(references);
4161 if (preformatted_text != NULL) {
4162 msg->cm_fields[eMesageText] = preformatted_text;
4165 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4174 * Validate recipients, count delivery types and errors, and handle aliasing
4175 * FIXME check for dupes!!!!!
4177 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4178 * were specified, or the number of addresses found invalid.
4180 * Caller needs to free the result using free_recipients()
4182 struct recptypes *validate_recipients(const char *supplied_recipients,
4183 const char *RemoteIdentifier,
4185 struct CitContext *CCC = CC;
4186 struct recptypes *ret;
4187 char *recipients = NULL;
4189 char this_recp[256];
4190 char this_recp_cooked[256];
4197 struct ctdluser tempUS;
4198 struct ctdlroom tempQR;
4199 struct ctdlroom tempQR2;
4205 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4206 if (ret == NULL) return(NULL);
4208 /* Set all strings to null and numeric values to zero */
4209 memset(ret, 0, sizeof(struct recptypes));
4211 if (supplied_recipients == NULL) {
4212 recipients = strdup("");
4215 recipients = strdup(supplied_recipients);
4218 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4219 * actually need, but it's healthier for the heap than doing lots of tiny
4220 * realloc() calls instead.
4222 len = strlen(recipients) + 1024;
4223 ret->errormsg = malloc(len);
4224 ret->recp_local = malloc(len);
4225 ret->recp_internet = malloc(len);
4226 ret->recp_ignet = malloc(len);
4227 ret->recp_room = malloc(len);
4228 ret->display_recp = malloc(len);
4229 ret->recp_orgroom = malloc(len);
4230 org_recp = malloc(len);
4232 ret->errormsg[0] = 0;
4233 ret->recp_local[0] = 0;
4234 ret->recp_internet[0] = 0;
4235 ret->recp_ignet[0] = 0;
4236 ret->recp_room[0] = 0;
4237 ret->recp_orgroom[0] = 0;
4238 ret->display_recp[0] = 0;
4240 ret->recptypes_magic = RECPTYPES_MAGIC;
4242 /* Change all valid separator characters to commas */
4243 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4244 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4245 recipients[i] = ',';
4249 /* Now start extracting recipients... */
4251 while (!IsEmptyStr(recipients)) {
4252 for (i=0; i<=strlen(recipients); ++i) {
4253 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4254 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4255 safestrncpy(this_recp, recipients, i+1);
4257 if (recipients[i] == ',') {
4258 strcpy(recipients, &recipients[i+1]);
4261 strcpy(recipients, "");
4268 if (IsEmptyStr(this_recp))
4270 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4273 strcpy(org_recp, this_recp);
4276 mailtype = alias(this_recp);
4278 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4279 if (this_recp[j]=='_') {
4280 this_recp_cooked[j] = ' ';
4283 this_recp_cooked[j] = this_recp[j];
4286 this_recp_cooked[j] = '\0';
4291 if (!strcasecmp(this_recp, "sysop")) {
4293 strcpy(this_recp, config.c_aideroom);
4294 if (!IsEmptyStr(ret->recp_room)) {
4295 strcat(ret->recp_room, "|");
4297 strcat(ret->recp_room, this_recp);
4299 else if ( (!strncasecmp(this_recp, "room_", 5))
4300 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4302 /* Save room so we can restore it later */
4303 tempQR2 = CCC->room;
4306 /* Check permissions to send mail to this room */
4307 err = CtdlDoIHavePermissionToPostInThisRoom(
4312 0 /* 0 = not a reply */
4321 if (!IsEmptyStr(ret->recp_room)) {
4322 strcat(ret->recp_room, "|");
4324 strcat(ret->recp_room, &this_recp_cooked[5]);
4326 if (!IsEmptyStr(ret->recp_orgroom)) {
4327 strcat(ret->recp_orgroom, "|");
4329 strcat(ret->recp_orgroom, org_recp);
4333 /* Restore room in case something needs it */
4334 CCC->room = tempQR2;
4337 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4339 strcpy(this_recp, tempUS.fullname);
4340 if (!IsEmptyStr(ret->recp_local)) {
4341 strcat(ret->recp_local, "|");
4343 strcat(ret->recp_local, this_recp);
4345 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4347 strcpy(this_recp, tempUS.fullname);
4348 if (!IsEmptyStr(ret->recp_local)) {
4349 strcat(ret->recp_local, "|");
4351 strcat(ret->recp_local, this_recp);
4359 /* Yes, you're reading this correctly: if the target
4360 * domain points back to the local system or an attached
4361 * Citadel directory, the address is invalid. That's
4362 * because if the address were valid, we would have
4363 * already translated it to a local address by now.
4365 if (IsDirectory(this_recp, 0)) {
4370 ++ret->num_internet;
4371 if (!IsEmptyStr(ret->recp_internet)) {
4372 strcat(ret->recp_internet, "|");
4374 strcat(ret->recp_internet, this_recp);
4379 if (!IsEmptyStr(ret->recp_ignet)) {
4380 strcat(ret->recp_ignet, "|");
4382 strcat(ret->recp_ignet, this_recp);
4390 if (IsEmptyStr(errmsg)) {
4391 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4394 snprintf(append, sizeof append, "%s", errmsg);
4396 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4397 if (!IsEmptyStr(ret->errormsg)) {
4398 strcat(ret->errormsg, "; ");
4400 strcat(ret->errormsg, append);
4404 if (IsEmptyStr(ret->display_recp)) {
4405 strcpy(append, this_recp);
4408 snprintf(append, sizeof append, ", %s", this_recp);
4410 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4411 strcat(ret->display_recp, append);
4417 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4418 ret->num_room + ret->num_error) == 0) {
4419 ret->num_error = (-1);
4420 strcpy(ret->errormsg, "No recipients specified.");
4423 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4424 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4425 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4426 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4427 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4428 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4436 * Destructor for struct recptypes
4438 void free_recipients(struct recptypes *valid) {
4440 if (valid == NULL) {
4444 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4445 struct CitContext *CCC = CC;
4446 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4450 if (valid->errormsg != NULL) free(valid->errormsg);
4451 if (valid->recp_local != NULL) free(valid->recp_local);
4452 if (valid->recp_internet != NULL) free(valid->recp_internet);
4453 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4454 if (valid->recp_room != NULL) free(valid->recp_room);
4455 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4456 if (valid->display_recp != NULL) free(valid->display_recp);
4457 if (valid->bounce_to != NULL) free(valid->bounce_to);
4458 if (valid->envelope_from != NULL) free(valid->envelope_from);
4459 if (valid->sending_room != NULL) free(valid->sending_room);
4466 * message entry - mode 0 (normal)
4468 void cmd_ent0(char *entargs)
4470 struct CitContext *CCC = CC;
4475 char supplied_euid[128];
4477 int format_type = 0;
4478 char newusername[256];
4479 char newuseremail[256];
4480 struct CtdlMessage *msg;
4484 struct recptypes *valid = NULL;
4485 struct recptypes *valid_to = NULL;
4486 struct recptypes *valid_cc = NULL;
4487 struct recptypes *valid_bcc = NULL;
4489 int subject_required = 0;
4494 int newuseremail_ok = 0;
4495 char references[SIZ];
4500 post = extract_int(entargs, 0);
4501 extract_token(recp, entargs, 1, '|', sizeof recp);
4502 anon_flag = extract_int(entargs, 2);
4503 format_type = extract_int(entargs, 3);
4504 extract_token(subject, entargs, 4, '|', sizeof subject);
4505 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4506 do_confirm = extract_int(entargs, 6);
4507 extract_token(cc, entargs, 7, '|', sizeof cc);
4508 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4509 switch(CC->room.QRdefaultview) {
4512 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4515 supplied_euid[0] = 0;
4518 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4519 extract_token(references, entargs, 11, '|', sizeof references);
4520 for (ptr=references; *ptr != 0; ++ptr) {
4521 if (*ptr == '!') *ptr = '|';
4524 /* first check to make sure the request is valid. */
4526 err = CtdlDoIHavePermissionToPostInThisRoom(
4531 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4535 cprintf("%d %s\n", err, errmsg);
4539 /* Check some other permission type things. */
4541 if (IsEmptyStr(newusername)) {
4542 strcpy(newusername, CCC->user.fullname);
4544 if ( (CCC->user.axlevel < AxAideU)
4545 && (strcasecmp(newusername, CCC->user.fullname))
4546 && (strcasecmp(newusername, CCC->cs_inet_fn))
4548 cprintf("%d You don't have permission to author messages as '%s'.\n",
4549 ERROR + HIGHER_ACCESS_REQUIRED,
4556 if (IsEmptyStr(newuseremail)) {
4557 newuseremail_ok = 1;
4560 if (!IsEmptyStr(newuseremail)) {
4561 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4562 newuseremail_ok = 1;
4564 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4565 j = num_tokens(CCC->cs_inet_other_emails, '|');
4566 for (i=0; i<j; ++i) {
4567 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4568 if (!strcasecmp(newuseremail, buf)) {
4569 newuseremail_ok = 1;
4575 if (!newuseremail_ok) {
4576 cprintf("%d You don't have permission to author messages as '%s'.\n",
4577 ERROR + HIGHER_ACCESS_REQUIRED,
4583 CCC->cs_flags |= CS_POSTING;
4585 /* In mailbox rooms we have to behave a little differently --
4586 * make sure the user has specified at least one recipient. Then
4587 * validate the recipient(s). We do this for the Mail> room, as
4588 * well as any room which has the "Mailbox" view set - unless it
4589 * is the DRAFTS room which does not require recipients
4592 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4593 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4594 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4595 if (CCC->user.axlevel < AxProbU) {
4596 strcpy(recp, "sysop");
4601 valid_to = validate_recipients(recp, NULL, 0);
4602 if (valid_to->num_error > 0) {
4603 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4604 free_recipients(valid_to);
4608 valid_cc = validate_recipients(cc, NULL, 0);
4609 if (valid_cc->num_error > 0) {
4610 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4611 free_recipients(valid_to);
4612 free_recipients(valid_cc);
4616 valid_bcc = validate_recipients(bcc, NULL, 0);
4617 if (valid_bcc->num_error > 0) {
4618 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4619 free_recipients(valid_to);
4620 free_recipients(valid_cc);
4621 free_recipients(valid_bcc);
4625 /* Recipient required, but none were specified */
4626 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4627 free_recipients(valid_to);
4628 free_recipients(valid_cc);
4629 free_recipients(valid_bcc);
4630 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4634 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4635 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4636 cprintf("%d You do not have permission "
4637 "to send Internet mail.\n",
4638 ERROR + HIGHER_ACCESS_REQUIRED);
4639 free_recipients(valid_to);
4640 free_recipients(valid_cc);
4641 free_recipients(valid_bcc);
4646 if ( ( (valid_to->num_internet + valid_to->num_ignet + valid_cc->num_internet + valid_cc->num_ignet + valid_bcc->num_internet + valid_bcc->num_ignet) > 0)
4647 && (CCC->user.axlevel < AxNetU) ) {
4648 cprintf("%d Higher access required for network mail.\n",
4649 ERROR + HIGHER_ACCESS_REQUIRED);
4650 free_recipients(valid_to);
4651 free_recipients(valid_cc);
4652 free_recipients(valid_bcc);
4656 if ((RESTRICT_INTERNET == 1)
4657 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4658 && ((CCC->user.flags & US_INTERNET) == 0)
4659 && (!CCC->internal_pgm)) {
4660 cprintf("%d You don't have access to Internet mail.\n",
4661 ERROR + HIGHER_ACCESS_REQUIRED);
4662 free_recipients(valid_to);
4663 free_recipients(valid_cc);
4664 free_recipients(valid_bcc);
4670 /* Is this a room which has anonymous-only or anonymous-option? */
4671 anonymous = MES_NORMAL;
4672 if (CCC->room.QRflags & QR_ANONONLY) {
4673 anonymous = MES_ANONONLY;
4675 if (CCC->room.QRflags & QR_ANONOPT) {
4676 if (anon_flag == 1) { /* only if the user requested it */
4677 anonymous = MES_ANONOPT;
4681 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4685 /* Recommend to the client that the use of a message subject is
4686 * strongly recommended in this room, if either the SUBJECTREQ flag
4687 * is set, or if there is one or more Internet email recipients.
4689 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4690 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4691 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4692 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4694 /* If we're only checking the validity of the request, return
4695 * success without creating the message.
4698 cprintf("%d %s|%d\n", CIT_OK,
4699 ((valid_to != NULL) ? valid_to->display_recp : ""),
4701 free_recipients(valid_to);
4702 free_recipients(valid_cc);
4703 free_recipients(valid_bcc);
4707 /* We don't need these anymore because we'll do it differently below */
4708 free_recipients(valid_to);
4709 free_recipients(valid_cc);
4710 free_recipients(valid_bcc);
4712 /* Read in the message from the client. */
4714 cprintf("%d send message\n", START_CHAT_MODE);
4716 cprintf("%d send message\n", SEND_LISTING);
4719 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4720 CCC->room.QRname, anonymous, format_type,
4721 newusername, newuseremail, subject,
4722 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4725 /* Put together one big recipients struct containing to/cc/bcc all in
4726 * one. This is for the envelope.
4728 char *all_recps = malloc(SIZ * 3);
4729 strcpy(all_recps, recp);
4730 if (!IsEmptyStr(cc)) {
4731 if (!IsEmptyStr(all_recps)) {
4732 strcat(all_recps, ",");
4734 strcat(all_recps, cc);
4736 if (!IsEmptyStr(bcc)) {
4737 if (!IsEmptyStr(all_recps)) {
4738 strcat(all_recps, ",");
4740 strcat(all_recps, bcc);
4742 if (!IsEmptyStr(all_recps)) {
4743 valid = validate_recipients(all_recps, NULL, 0);
4750 if ((valid != NULL) && (valid->num_room == 1))
4752 /* posting into an ML room? set the envelope from
4753 * to the actual mail address so others get a valid
4756 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4760 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4762 cprintf("%ld\n", msgnum);
4764 if (StrLength(CCC->StatusMessage) > 0) {
4765 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4767 else if (msgnum >= 0L) {
4768 client_write(HKEY("Message accepted.\n"));
4771 client_write(HKEY("Internal error.\n"));
4774 if (!CM_IsEmpty(msg, eExclusiveID)) {
4775 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4784 if (valid != NULL) {
4785 free_recipients(valid);
4793 * API function to delete messages which match a set of criteria
4794 * (returns the actual number of messages deleted)
4796 int CtdlDeleteMessages(char *room_name, /* which room */
4797 long *dmsgnums, /* array of msg numbers to be deleted */
4798 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4799 char *content_type /* or "" for any. regular expressions expected. */
4802 struct CitContext *CCC = CC;
4803 struct ctdlroom qrbuf;
4804 struct cdbdata *cdbfr;
4805 long *msglist = NULL;
4806 long *dellist = NULL;
4809 int num_deleted = 0;
4811 struct MetaData smi;
4814 int need_to_free_re = 0;
4816 if (content_type) if (!IsEmptyStr(content_type)) {
4817 regcomp(&re, content_type, 0);
4818 need_to_free_re = 1;
4820 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4821 room_name, num_dmsgnums, content_type);
4823 /* get room record, obtaining a lock... */
4824 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4825 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4827 if (need_to_free_re) regfree(&re);
4828 return (0); /* room not found */
4830 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4832 if (cdbfr != NULL) {
4833 dellist = malloc(cdbfr->len);
4834 msglist = (long *) cdbfr->ptr;
4835 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4836 num_msgs = cdbfr->len / sizeof(long);
4840 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4841 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4842 int have_more_del = 1;
4844 num_msgs = sort_msglist(msglist, num_msgs);
4845 if (num_dmsgnums > 1)
4846 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4849 StrBuf *dbg = NewStrBuf();
4850 for (i = 0; i < num_dmsgnums; i++)
4851 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4852 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
4857 while ((i < num_msgs) && (have_more_del)) {
4860 /* Set/clear a bit for each criterion */
4862 /* 0 messages in the list or a null list means that we are
4863 * interested in deleting any messages which meet the other criteria.
4866 delete_this |= 0x01;
4869 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4874 if (msglist[i] == dmsgnums[j]) {
4875 delete_this |= 0x01;
4878 have_more_del = (j < num_dmsgnums);
4881 if (have_contenttype) {
4882 GetMetaData(&smi, msglist[i]);
4883 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4884 delete_this |= 0x02;
4887 delete_this |= 0x02;
4890 /* Delete message only if all bits are set */
4891 if (delete_this == 0x03) {
4892 dellist[num_deleted++] = msglist[i];
4899 StrBuf *dbg = NewStrBuf();
4900 for (i = 0; i < num_deleted; i++)
4901 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4902 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
4906 num_msgs = sort_msglist(msglist, num_msgs);
4907 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4908 msglist, (int)(num_msgs * sizeof(long)));
4911 qrbuf.QRhighest = msglist[num_msgs - 1];
4913 qrbuf.QRhighest = 0;
4915 CtdlPutRoomLock(&qrbuf);
4917 /* Go through the messages we pulled out of the index, and decrement
4918 * their reference counts by 1. If this is the only room the message
4919 * was in, the reference count will reach zero and the message will
4920 * automatically be deleted from the database. We do this in a
4921 * separate pass because there might be plug-in hooks getting called,
4922 * and we don't want that happening during an S_ROOMS critical
4926 for (i=0; i<num_deleted; ++i) {
4927 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4929 AdjRefCountList(dellist, num_deleted, -1);
4931 /* Now free the memory we used, and go away. */
4932 if (msglist != NULL) free(msglist);
4933 if (dellist != NULL) free(dellist);
4934 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
4935 if (need_to_free_re) regfree(&re);
4936 return (num_deleted);
4940 * Delete message from current room
4942 void cmd_dele(char *args)
4951 extract_token(msgset, args, 0, '|', sizeof msgset);
4952 num_msgs = num_tokens(msgset, ',');
4954 cprintf("%d Nothing to do.\n", CIT_OK);
4958 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4959 cprintf("%d Higher access required.\n",
4960 ERROR + HIGHER_ACCESS_REQUIRED);
4965 * Build our message set to be moved/copied
4967 msgs = malloc(num_msgs * sizeof(long));
4968 for (i=0; i<num_msgs; ++i) {
4969 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4970 msgs[i] = atol(msgtok);
4973 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4977 cprintf("%d %d message%s deleted.\n", CIT_OK,
4978 num_deleted, ((num_deleted != 1) ? "s" : ""));
4980 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4988 * move or copy a message to another room
4990 void cmd_move(char *args)
4997 char targ[ROOMNAMELEN];
4998 struct ctdlroom qtemp;
5005 extract_token(msgset, args, 0, '|', sizeof msgset);
5006 num_msgs = num_tokens(msgset, ',');
5008 cprintf("%d Nothing to do.\n", CIT_OK);
5012 extract_token(targ, args, 1, '|', sizeof targ);
5013 convert_room_name_macros(targ, sizeof targ);
5014 targ[ROOMNAMELEN - 1] = 0;
5015 is_copy = extract_int(args, 2);
5017 if (CtdlGetRoom(&qtemp, targ) != 0) {
5018 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5022 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5023 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5027 CtdlGetUser(&CC->user, CC->curr_user);
5028 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5030 /* Check for permission to perform this operation.
5031 * Remember: "CC->room" is source, "qtemp" is target.
5035 /* Admins can move/copy */
5036 if (CC->user.axlevel >= AxAideU) permit = 1;
5038 /* Room aides can move/copy */
5039 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5041 /* Permit move/copy from personal rooms */
5042 if ((CC->room.QRflags & QR_MAILBOX)
5043 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5045 /* Permit only copy from public to personal room */
5047 && (!(CC->room.QRflags & QR_MAILBOX))
5048 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5050 /* Permit message removal from collaborative delete rooms */
5051 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5053 /* Users allowed to post into the target room may move into it too. */
5054 if ((CC->room.QRflags & QR_MAILBOX) &&
5055 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5057 /* User must have access to target room */
5058 if (!(ra & UA_KNOWN)) permit = 0;
5061 cprintf("%d Higher access required.\n",
5062 ERROR + HIGHER_ACCESS_REQUIRED);
5067 * Build our message set to be moved/copied
5069 msgs = malloc(num_msgs * sizeof(long));
5070 for (i=0; i<num_msgs; ++i) {
5071 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5072 msgs[i] = atol(msgtok);
5078 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5080 cprintf("%d Cannot store message(s) in %s: error %d\n",
5086 /* Now delete the message from the source room,
5087 * if this is a 'move' rather than a 'copy' operation.
5090 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5094 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5100 * GetMetaData() - Get the supplementary record for a message
5102 void GetMetaData(struct MetaData *smibuf, long msgnum)
5105 struct cdbdata *cdbsmi;
5108 memset(smibuf, 0, sizeof(struct MetaData));
5109 smibuf->meta_msgnum = msgnum;
5110 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5112 /* Use the negative of the message number for its supp record index */
5113 TheIndex = (0L - msgnum);
5115 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5116 if (cdbsmi == NULL) {
5117 return; /* record not found; go with defaults */
5119 memcpy(smibuf, cdbsmi->ptr,
5120 ((cdbsmi->len > sizeof(struct MetaData)) ?
5121 sizeof(struct MetaData) : cdbsmi->len));
5128 * PutMetaData() - (re)write supplementary record for a message
5130 void PutMetaData(struct MetaData *smibuf)
5134 /* Use the negative of the message number for the metadata db index */
5135 TheIndex = (0L - smibuf->meta_msgnum);
5137 cdb_store(CDB_MSGMAIN,
5138 &TheIndex, (int)sizeof(long),
5139 smibuf, (int)sizeof(struct MetaData));
5144 * AdjRefCount - submit an adjustment to the reference count for a message.
5145 * (These are just queued -- we actually process them later.)
5147 void AdjRefCount(long msgnum, int incr)
5149 struct CitContext *CCC = CC;
5150 struct arcq new_arcq;
5153 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5155 begin_critical_section(S_SUPPMSGMAIN);
5156 if (arcfp == NULL) {
5157 arcfp = fopen(file_arcq, "ab+");
5158 chown(file_arcq, CTDLUID, (-1));
5159 chmod(file_arcq, 0600);
5161 end_critical_section(S_SUPPMSGMAIN);
5163 /* msgnum < 0 means that we're trying to close the file */
5165 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5166 begin_critical_section(S_SUPPMSGMAIN);
5167 if (arcfp != NULL) {
5171 end_critical_section(S_SUPPMSGMAIN);
5176 * If we can't open the queue, perform the operation synchronously.
5178 if (arcfp == NULL) {
5179 TDAP_AdjRefCount(msgnum, incr);
5183 new_arcq.arcq_msgnum = msgnum;
5184 new_arcq.arcq_delta = incr;
5185 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5187 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5196 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5198 struct CitContext *CCC = CC;
5199 long i, the_size, offset;
5200 struct arcq *new_arcq;
5203 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5205 begin_critical_section(S_SUPPMSGMAIN);
5206 if (arcfp == NULL) {
5207 arcfp = fopen(file_arcq, "ab+");
5208 chown(file_arcq, CTDLUID, (-1));
5209 chmod(file_arcq, 0600);
5211 end_critical_section(S_SUPPMSGMAIN);
5214 * If we can't open the queue, perform the operation synchronously.
5216 if (arcfp == NULL) {
5217 for (i = 0; i < nmsg; i++)
5218 TDAP_AdjRefCount(msgnum[i], incr);
5222 the_size = sizeof(struct arcq) * nmsg;
5223 new_arcq = malloc(the_size);
5224 for (i = 0; i < nmsg; i++) {
5225 new_arcq[i].arcq_msgnum = msgnum[i];
5226 new_arcq[i].arcq_delta = incr;
5230 while ((rv >= 0) && (offset < the_size))
5232 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5234 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5250 * TDAP_ProcessAdjRefCountQueue()
5252 * Process the queue of message count adjustments that was created by calls
5253 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5254 * for each one. This should be an "off hours" operation.
5256 int TDAP_ProcessAdjRefCountQueue(void)
5258 struct CitContext *CCC = CC;
5259 char file_arcq_temp[PATH_MAX];
5262 struct arcq arcq_rec;
5263 int num_records_processed = 0;
5265 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5267 begin_critical_section(S_SUPPMSGMAIN);
5268 if (arcfp != NULL) {
5273 r = link(file_arcq, file_arcq_temp);
5275 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5276 end_critical_section(S_SUPPMSGMAIN);
5277 return(num_records_processed);
5281 end_critical_section(S_SUPPMSGMAIN);
5283 fp = fopen(file_arcq_temp, "rb");
5285 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5286 return(num_records_processed);
5289 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5290 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5291 ++num_records_processed;
5295 r = unlink(file_arcq_temp);
5297 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5300 return(num_records_processed);
5306 * TDAP_AdjRefCount - adjust the reference count for a message.
5307 * This one does it "for real" because it's called by
5308 * the autopurger function that processes the queue
5309 * created by AdjRefCount(). If a message's reference
5310 * count becomes zero, we also delete the message from
5311 * disk and de-index it.
5313 void TDAP_AdjRefCount(long msgnum, int incr)
5315 struct CitContext *CCC = CC;
5317 struct MetaData smi;
5320 /* This is a *tight* critical section; please keep it that way, as
5321 * it may get called while nested in other critical sections.
5322 * Complicating this any further will surely cause deadlock!
5324 begin_critical_section(S_SUPPMSGMAIN);
5325 GetMetaData(&smi, msgnum);
5326 smi.meta_refcount += incr;
5328 end_critical_section(S_SUPPMSGMAIN);
5329 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5330 msgnum, incr, smi.meta_refcount
5333 /* If the reference count is now zero, delete the message
5334 * (and its supplementary record as well).
5336 if (smi.meta_refcount == 0) {
5337 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5339 /* Call delete hooks with NULL room to show it has gone altogether */
5340 PerformDeleteHooks(NULL, msgnum);
5342 /* Remove from message base */
5344 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5345 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5347 /* Remove metadata record */
5348 delnum = (0L - msgnum);
5349 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5355 * Write a generic object to this room
5357 * Note: this could be much more efficient. Right now we use two temporary
5358 * files, and still pull the message into memory as with all others.
5360 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5361 char *content_type, /* MIME type of this object */
5362 char *raw_message, /* Data to be written */
5363 off_t raw_length, /* Size of raw_message */
5364 struct ctdluser *is_mailbox, /* Mailbox room? */
5365 int is_binary, /* Is encoding necessary? */
5366 int is_unique, /* Del others of this type? */
5367 unsigned int flags /* Internal save flags */
5370 struct CitContext *CCC = CC;
5371 struct ctdlroom qrbuf;
5372 char roomname[ROOMNAMELEN];
5373 struct CtdlMessage *msg;
5374 char *encoded_message = NULL;
5376 if (is_mailbox != NULL) {
5377 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5380 safestrncpy(roomname, req_room, sizeof(roomname));
5383 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5386 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5389 encoded_message = malloc((size_t)(raw_length + 4096));
5392 sprintf(encoded_message, "Content-type: %s\n", content_type);
5395 sprintf(&encoded_message[strlen(encoded_message)],
5396 "Content-transfer-encoding: base64\n\n"
5400 sprintf(&encoded_message[strlen(encoded_message)],
5401 "Content-transfer-encoding: 7bit\n\n"
5407 &encoded_message[strlen(encoded_message)],
5415 &encoded_message[strlen(encoded_message)],
5421 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5422 msg = malloc(sizeof(struct CtdlMessage));
5423 memset(msg, 0, sizeof(struct CtdlMessage));
5424 msg->cm_magic = CTDLMESSAGE_MAGIC;
5425 msg->cm_anon_type = MES_NORMAL;
5426 msg->cm_format_type = 4;
5427 msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
5428 msg->cm_fields[eOriginalRoom] = strdup(req_room);
5429 msg->cm_fields[eNodeName] = strdup(config.c_nodename);
5430 msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
5431 msg->cm_flags = flags;
5433 msg->cm_fields[eMesageText] = encoded_message;
5435 /* Create the requested room if we have to. */
5436 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5437 CtdlCreateRoom(roomname,
5438 ( (is_mailbox != NULL) ? 5 : 3 ),
5439 "", 0, 1, 0, VIEW_BBS);
5441 /* If the caller specified this object as unique, delete all
5442 * other objects of this type that are currently in the room.
5445 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5446 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5449 /* Now write the data */
5450 CtdlSubmitMsg(msg, NULL, roomname, 0);
5459 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5460 config_msgnum = msgnum;
5464 char *CtdlGetSysConfig(char *sysconfname) {
5465 char hold_rm[ROOMNAMELEN];
5468 struct CtdlMessage *msg;
5471 strcpy(hold_rm, CC->room.QRname);
5472 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5473 CtdlGetRoom(&CC->room, hold_rm);
5478 /* We want the last (and probably only) config in this room */
5479 begin_critical_section(S_CONFIG);
5480 config_msgnum = (-1L);
5481 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5482 CtdlGetSysConfigBackend, NULL);
5483 msgnum = config_msgnum;
5484 end_critical_section(S_CONFIG);
5490 msg = CtdlFetchMessage(msgnum, 1);
5492 conf = strdup(msg->cm_fields[eMesageText]);
5500 CtdlGetRoom(&CC->room, hold_rm);
5502 if (conf != NULL) do {
5503 extract_token(buf, conf, 0, '\n', sizeof buf);
5504 strcpy(conf, &conf[strlen(buf)+1]);
5505 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5511 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5512 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5517 * Determine whether a given Internet address belongs to the current user
5519 int CtdlIsMe(char *addr, int addr_buf_len)
5521 struct recptypes *recp;
5524 recp = validate_recipients(addr, NULL, 0);
5525 if (recp == NULL) return(0);
5527 if (recp->num_local == 0) {
5528 free_recipients(recp);
5532 for (i=0; i<recp->num_local; ++i) {
5533 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5534 if (!strcasecmp(addr, CC->user.fullname)) {
5535 free_recipients(recp);
5540 free_recipients(recp);
5546 * Citadel protocol command to do the same
5548 void cmd_isme(char *argbuf) {
5551 if (CtdlAccessCheck(ac_logged_in)) return;
5552 extract_token(addr, argbuf, 0, '|', sizeof addr);
5554 if (CtdlIsMe(addr, sizeof addr)) {
5555 cprintf("%d %s\n", CIT_OK, addr);
5558 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5564 /*****************************************************************************/
5565 /* MODULE INITIALIZATION STUFF */
5566 /*****************************************************************************/
5567 void SetMessageDebugEnabled(const int n)
5569 MessageDebugEnabled = n;
5571 CTDL_MODULE_INIT(msgbase)
5574 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5576 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5577 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5578 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5579 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5580 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5581 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5582 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5583 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5584 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5585 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5586 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5587 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5590 /* return our Subversion id for the Log */