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 is_valid_message(struct CtdlMessage *msg) {
278 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
279 struct CitContext *CCC = CC;
280 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
286 void CtdlFreeMessageContents(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 CtdlFreeMessage(struct CtdlMessage *msg)
302 if (is_valid_message(msg) == 0)
304 if (msg != NULL) free (msg);
307 CtdlFreeMessageContents(msg);
311 int DupCMField(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 * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
326 struct CtdlMessage *NewMsg;
328 if (is_valid_message(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 (!DupCMField(i, OrgMsg, NewMsg))
344 CtdlFreeMessage(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] : "")
525 CtdlFreeMessage(msg);
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"));
545 CtdlFreeMessage(msg);
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)) {
945 CtdlFreeMessage(msg);
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) CtdlFreeMessage(template);
1163 * help_subst() - support routine for help file viewer
1165 void help_subst(char *strbuf, char *source, char *dest)
1170 while (p = pattern2(strbuf, source), (p >= 0)) {
1171 strcpy(workbuf, &strbuf[p + strlen(source)]);
1172 strcpy(&strbuf[p], dest);
1173 strcat(strbuf, workbuf);
1178 void do_help_subst(char *buffer)
1182 help_subst(buffer, "^nodename", config.c_nodename);
1183 help_subst(buffer, "^humannode", config.c_humannode);
1184 help_subst(buffer, "^fqdn", config.c_fqdn);
1185 help_subst(buffer, "^username", CC->user.fullname);
1186 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
1187 help_subst(buffer, "^usernum", buf2);
1188 help_subst(buffer, "^sysadm", config.c_sysadm);
1189 help_subst(buffer, "^variantname", CITADEL);
1190 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
1191 help_subst(buffer, "^maxsessions", buf2);
1192 help_subst(buffer, "^bbsdir", ctdl_message_dir);
1198 * memfmout() - Citadel text formatter and paginator.
1199 * Although the original purpose of this routine was to format
1200 * text to the reader's screen width, all we're really using it
1201 * for here is to format text out to 80 columns before sending it
1202 * to the client. The client software may reformat it again.
1205 char *mptr, /* where are we going to get our text from? */
1206 const char *nl /* string to terminate lines with */
1208 struct CitContext *CCC = CC;
1210 unsigned char ch = 0;
1217 while (ch=*(mptr++), ch != 0) {
1220 if (client_write(outbuf, len) == -1)
1222 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1226 if (client_write(nl, nllen) == -1)
1228 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1233 else if (ch == '\r') {
1234 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1236 else if (isspace(ch)) {
1237 if (column > 72) { /* Beyond 72 columns, break on the next space */
1238 if (client_write(outbuf, len) == -1)
1240 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1244 if (client_write(nl, nllen) == -1)
1246 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1259 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1260 if (client_write(outbuf, len) == -1)
1262 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1266 if (client_write(nl, nllen) == -1)
1268 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1276 if (client_write(outbuf, len) == -1)
1278 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1282 client_write(nl, nllen);
1290 * Callback function for mime parser that simply lists the part
1292 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1293 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1294 char *cbid, void *cbuserdata)
1298 ma = (struct ma_info *)cbuserdata;
1299 if (ma->is_ma == 0) {
1300 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1313 * Callback function for multipart prefix
1315 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1316 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1317 char *cbid, void *cbuserdata)
1321 ma = (struct ma_info *)cbuserdata;
1322 if (!strcasecmp(cbtype, "multipart/alternative")) {
1326 if (ma->is_ma == 0) {
1327 cprintf("pref=%s|%s\n", partnum, cbtype);
1332 * Callback function for multipart sufffix
1334 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1335 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1336 char *cbid, void *cbuserdata)
1340 ma = (struct ma_info *)cbuserdata;
1341 if (ma->is_ma == 0) {
1342 cprintf("suff=%s|%s\n", partnum, cbtype);
1344 if (!strcasecmp(cbtype, "multipart/alternative")) {
1351 * Callback function for mime parser that opens a section for downloading
1353 void mime_download(char *name, char *filename, char *partnum, char *disp,
1354 void *content, char *cbtype, char *cbcharset, size_t length,
1355 char *encoding, char *cbid, void *cbuserdata)
1358 CitContext *CCC = MyContext();
1360 /* Silently go away if there's already a download open. */
1361 if (CCC->download_fp != NULL)
1365 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1366 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1368 CCC->download_fp = tmpfile();
1369 if (CCC->download_fp == NULL) {
1370 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1372 cprintf("%d cannot open temporary file: %s\n",
1373 ERROR + INTERNAL_ERROR, strerror(errno));
1377 rv = fwrite(content, length, 1, CCC->download_fp);
1379 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1381 cprintf("%d unable to write tempfile.\n",
1383 fclose(CCC->download_fp);
1384 CCC->download_fp = NULL;
1387 fflush(CCC->download_fp);
1388 rewind(CCC->download_fp);
1390 OpenCmdResult(filename, cbtype);
1397 * Callback function for mime parser that outputs a section all at once.
1398 * We can specify the desired section by part number *or* content-id.
1400 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1401 void *content, char *cbtype, char *cbcharset, size_t length,
1402 char *encoding, char *cbid, void *cbuserdata)
1404 int *found_it = (int *)cbuserdata;
1407 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1408 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1411 cprintf("%d %d|-1|%s|%s|%s\n",
1418 client_write(content, length);
1424 * Load a message from disk into memory.
1425 * This is used by CtdlOutputMsg() and other fetch functions.
1427 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1428 * using the CtdlMessageFree() function.
1430 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1432 struct CitContext *CCC = CC;
1433 struct cdbdata *dmsgtext;
1434 struct CtdlMessage *ret = NULL;
1438 cit_uint8_t field_header;
1440 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1441 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1442 if (dmsgtext == NULL) {
1443 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1446 mptr = dmsgtext->ptr;
1447 upper_bound = mptr + dmsgtext->len;
1449 /* Parse the three bytes that begin EVERY message on disk.
1450 * The first is always 0xFF, the on-disk magic number.
1451 * The second is the anonymous/public type byte.
1452 * The third is the format type byte (vari, fixed, or MIME).
1456 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1460 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1461 memset(ret, 0, sizeof(struct CtdlMessage));
1463 ret->cm_magic = CTDLMESSAGE_MAGIC;
1464 ret->cm_anon_type = *mptr++; /* Anon type byte */
1465 ret->cm_format_type = *mptr++; /* Format type byte */
1468 * The rest is zero or more arbitrary fields. Load them in.
1469 * We're done when we encounter either a zero-length field or
1470 * have just processed the 'M' (message text) field.
1474 if (mptr >= upper_bound) {
1477 field_header = *mptr++;
1479 CM_SetField(ret, field_header, mptr, len);
1481 mptr += len + 1; /* advance to next field */
1483 } while ((mptr < upper_bound) && (field_header != 'M'));
1487 /* Always make sure there's something in the msg text field. If
1488 * it's NULL, the message text is most likely stored separately,
1489 * so go ahead and fetch that. Failing that, just set a dummy
1490 * body so other code doesn't barf.
1492 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1493 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1494 if (dmsgtext != NULL) {
1495 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1499 if (CM_IsEmpty(ret, eMesageText)) {
1500 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1503 /* Perform "before read" hooks (aborting if any return nonzero) */
1504 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1505 CtdlFreeMessage(ret);
1515 * Pre callback function for multipart/alternative
1517 * NOTE: this differs from the standard behavior for a reason. Normally when
1518 * displaying multipart/alternative you want to show the _last_ usable
1519 * format in the message. Here we show the _first_ one, because it's
1520 * usually text/plain. Since this set of functions is designed for text
1521 * output to non-MIME-aware clients, this is the desired behavior.
1524 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1525 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1526 char *cbid, void *cbuserdata)
1528 struct CitContext *CCC = CC;
1531 ma = (struct ma_info *)cbuserdata;
1532 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1533 if (!strcasecmp(cbtype, "multipart/alternative")) {
1537 if (!strcasecmp(cbtype, "message/rfc822")) {
1543 * Post callback function for multipart/alternative
1545 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1546 void *content, char *cbtype, char *cbcharset, size_t length,
1547 char *encoding, char *cbid, void *cbuserdata)
1549 struct CitContext *CCC = CC;
1552 ma = (struct ma_info *)cbuserdata;
1553 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1554 if (!strcasecmp(cbtype, "multipart/alternative")) {
1558 if (!strcasecmp(cbtype, "message/rfc822")) {
1564 * Inline callback function for mime parser that wants to display text
1566 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1567 void *content, char *cbtype, char *cbcharset, size_t length,
1568 char *encoding, char *cbid, void *cbuserdata)
1570 struct CitContext *CCC = CC;
1576 ma = (struct ma_info *)cbuserdata;
1578 MSG_syslog(LOG_DEBUG,
1579 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1580 partnum, filename, cbtype, (long)length);
1583 * If we're in the middle of a multipart/alternative scope and
1584 * we've already printed another section, skip this one.
1586 if ( (ma->is_ma) && (ma->did_print) ) {
1587 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1592 if ( (!strcasecmp(cbtype, "text/plain"))
1593 || (IsEmptyStr(cbtype)) ) {
1596 client_write(wptr, length);
1597 if (wptr[length-1] != '\n') {
1604 if (!strcasecmp(cbtype, "text/html")) {
1605 ptr = html_to_ascii(content, length, 80, 0);
1607 client_write(ptr, wlen);
1608 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1615 if (ma->use_fo_hooks) {
1616 if (PerformFixedOutputHooks(cbtype, content, length)) {
1617 /* above function returns nonzero if it handled the part */
1622 if (strncasecmp(cbtype, "multipart/", 10)) {
1623 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1624 partnum, filename, cbtype, (long)length);
1630 * The client is elegant and sophisticated and wants to be choosy about
1631 * MIME content types, so figure out which multipart/alternative part
1632 * we're going to send.
1634 * We use a system of weights. When we find a part that matches one of the
1635 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1636 * and then set ma->chosen_pref to that MIME type's position in our preference
1637 * list. If we then hit another match, we only replace the first match if
1638 * the preference value is lower.
1640 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1641 void *content, char *cbtype, char *cbcharset, size_t length,
1642 char *encoding, char *cbid, void *cbuserdata)
1644 struct CitContext *CCC = CC;
1649 ma = (struct ma_info *)cbuserdata;
1651 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1652 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1653 // I don't know if there are any side effects! Please TEST TEST TEST
1654 //if (ma->is_ma > 0) {
1656 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1657 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1658 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1659 if (i < ma->chosen_pref) {
1660 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1661 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1662 ma->chosen_pref = i;
1669 * Now that we've chosen our preferred part, output it.
1671 void output_preferred(char *name,
1683 struct CitContext *CCC = CC;
1686 int add_newline = 0;
1689 char *decoded = NULL;
1690 size_t bytes_decoded;
1693 ma = (struct ma_info *)cbuserdata;
1695 /* This is not the MIME part you're looking for... */
1696 if (strcasecmp(partnum, ma->chosen_part)) return;
1698 /* If the content-type of this part is in our preferred formats
1699 * list, we can simply output it verbatim.
1701 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1702 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1703 if (!strcasecmp(buf, cbtype)) {
1704 /* Yeah! Go! W00t!! */
1705 if (ma->dont_decode == 0)
1706 rc = mime_decode_now (content,
1712 break; /* Give us the chance, maybe theres another one. */
1714 if (rc == 0) text_content = (char *)content;
1716 text_content = decoded;
1717 length = bytes_decoded;
1720 if (text_content[length-1] != '\n') {
1723 cprintf("Content-type: %s", cbtype);
1724 if (!IsEmptyStr(cbcharset)) {
1725 cprintf("; charset=%s", cbcharset);
1727 cprintf("\nContent-length: %d\n",
1728 (int)(length + add_newline) );
1729 if (!IsEmptyStr(encoding)) {
1730 cprintf("Content-transfer-encoding: %s\n", encoding);
1733 cprintf("Content-transfer-encoding: 7bit\n");
1735 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1737 if (client_write(text_content, length) == -1)
1739 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1742 if (add_newline) cprintf("\n");
1743 if (decoded != NULL) free(decoded);
1748 /* No translations required or possible: output as text/plain */
1749 cprintf("Content-type: text/plain\n\n");
1751 if (ma->dont_decode == 0)
1752 rc = mime_decode_now (content,
1758 return; /* Give us the chance, maybe theres another one. */
1760 if (rc == 0) text_content = (char *)content;
1762 text_content = decoded;
1763 length = bytes_decoded;
1766 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1767 length, encoding, cbid, cbuserdata);
1768 if (decoded != NULL) free(decoded);
1773 char desired_section[64];
1780 * Callback function for
1782 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1783 void *content, char *cbtype, char *cbcharset, size_t length,
1784 char *encoding, char *cbid, void *cbuserdata)
1786 struct encapmsg *encap;
1788 encap = (struct encapmsg *)cbuserdata;
1790 /* Only proceed if this is the desired section... */
1791 if (!strcasecmp(encap->desired_section, partnum)) {
1792 encap->msglen = length;
1793 encap->msg = malloc(length + 2);
1794 memcpy(encap->msg, content, length);
1801 * Determine whether the specified message exists in the cached_msglist
1802 * (This is a security check)
1804 int check_cached_msglist(long msgnum) {
1805 struct CitContext *CCC = CC;
1807 /* cases in which we skip the check */
1808 if (!CCC) return om_ok; /* not a session */
1809 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1810 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1811 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1814 /* Do a binary search within the cached_msglist for the requested msgnum */
1816 int max = (CC->cached_num_msgs - 1);
1818 while (max >= min) {
1819 int middle = min + (max-min) / 2 ;
1820 if (msgnum == CCC->cached_msglist[middle]) {
1823 if (msgnum > CC->cached_msglist[middle]) {
1831 return om_access_denied;
1836 * Determine whether the currently logged in session has permission to read
1837 * messages in the current room.
1839 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1840 if ( (!(CC->logged_in))
1841 && (!(CC->internal_pgm))
1842 && (!config.c_guest_logins)
1844 return(om_not_logged_in);
1851 * Get a message off disk. (returns om_* values found in msgbase.h)
1854 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1855 int mode, /* how would you like that message? */
1856 int headers_only, /* eschew the message body? */
1857 int do_proto, /* do Citadel protocol responses? */
1858 int crlf, /* Use CRLF newlines instead of LF? */
1859 char *section, /* NULL or a message/rfc822 section */
1860 int flags, /* various flags; see msgbase.h */
1864 struct CitContext *CCC = CC;
1865 struct CtdlMessage *TheMessage = NULL;
1866 int retcode = CIT_OK;
1867 struct encapmsg encap;
1870 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1872 (section ? section : "<>")
1875 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1878 if (r == om_not_logged_in) {
1879 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1882 cprintf("%d An unknown error has occurred.\n", ERROR);
1889 * Check to make sure the message is actually IN this room
1891 r = check_cached_msglist(msg_num);
1892 if (r == om_access_denied) {
1893 /* Not in the cache? We get ONE shot to check it again. */
1894 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1895 r = check_cached_msglist(msg_num);
1898 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1899 msg_num, CCC->room.QRname
1902 if (r == om_access_denied) {
1903 cprintf("%d message %ld was not found in this room\n",
1904 ERROR + HIGHER_ACCESS_REQUIRED,
1913 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1914 * request that we don't even bother loading the body into memory.
1916 if (headers_only == HEADERS_FAST) {
1917 TheMessage = CtdlFetchMessage(msg_num, 0);
1920 TheMessage = CtdlFetchMessage(msg_num, 1);
1923 if (TheMessage == NULL) {
1924 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1925 ERROR + MESSAGE_NOT_FOUND, msg_num);
1926 return(om_no_such_msg);
1929 /* Here is the weird form of this command, to process only an
1930 * encapsulated message/rfc822 section.
1932 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1933 memset(&encap, 0, sizeof encap);
1934 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1935 mime_parser(TheMessage->cm_fields[eMesageText],
1937 *extract_encapsulated_message,
1938 NULL, NULL, (void *)&encap, 0
1941 if ((Author != NULL) && (*Author == NULL))
1943 *Author = TheMessage->cm_fields[eAuthor];
1944 TheMessage->cm_fields[eAuthor] = NULL;
1946 if ((Address != NULL) && (*Address == NULL))
1948 *Address = TheMessage->cm_fields[erFc822Addr];
1949 TheMessage->cm_fields[erFc822Addr] = NULL;
1951 CtdlFreeMessage(TheMessage);
1955 encap.msg[encap.msglen] = 0;
1956 TheMessage = convert_internet_message(encap.msg);
1957 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1959 /* Now we let it fall through to the bottom of this
1960 * function, because TheMessage now contains the
1961 * encapsulated message instead of the top-level
1962 * message. Isn't that neat?
1967 cprintf("%d msg %ld has no part %s\n",
1968 ERROR + MESSAGE_NOT_FOUND,
1972 retcode = om_no_such_msg;
1977 /* Ok, output the message now */
1978 if (retcode == CIT_OK)
1979 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1980 if ((Author != NULL) && (*Author == NULL))
1982 *Author = TheMessage->cm_fields[eAuthor];
1983 TheMessage->cm_fields[eAuthor] = NULL;
1985 if ((Address != NULL) && (*Address == NULL))
1987 *Address = TheMessage->cm_fields[erFc822Addr];
1988 TheMessage->cm_fields[erFc822Addr] = NULL;
1991 CtdlFreeMessage(TheMessage);
1997 char *qp_encode_email_addrs(char *source)
1999 struct CitContext *CCC = CC;
2000 char *user, *node, *name;
2001 const char headerStr[] = "=?UTF-8?Q?";
2005 int need_to_encode = 0;
2011 long nAddrPtrMax = 50;
2016 if (source == NULL) return source;
2017 if (IsEmptyStr(source)) return source;
2018 if (MessageDebugEnabled != 0) cit_backtrace();
2019 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
2021 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
2022 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
2023 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
2026 while (!IsEmptyStr (&source[i])) {
2027 if (nColons >= nAddrPtrMax){
2030 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2031 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
2032 free (AddrPtr), AddrPtr = ptr;
2034 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2035 memset(&ptr[nAddrPtrMax], 0,
2036 sizeof (long) * nAddrPtrMax);
2038 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
2039 free (AddrUtf8), AddrUtf8 = ptr;
2042 if (((unsigned char) source[i] < 32) ||
2043 ((unsigned char) source[i] > 126)) {
2045 AddrUtf8[nColons] = 1;
2047 if (source[i] == '"')
2048 InQuotes = !InQuotes;
2049 if (!InQuotes && source[i] == ',') {
2050 AddrPtr[nColons] = i;
2055 if (need_to_encode == 0) {
2062 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
2063 Encoded = (char*) malloc (EncodedMaxLen);
2065 for (i = 0; i < nColons; i++)
2066 source[AddrPtr[i]++] = '\0';
2067 /* TODO: if libidn, this might get larger*/
2068 user = malloc(SourceLen + 1);
2069 node = malloc(SourceLen + 1);
2070 name = malloc(SourceLen + 1);
2074 for (i = 0; i < nColons && nPtr != NULL; i++) {
2075 nmax = EncodedMaxLen - (nPtr - Encoded);
2077 process_rfc822_addr(&source[AddrPtr[i]],
2081 /* TODO: libIDN here ! */
2082 if (IsEmptyStr(name)) {
2083 n = snprintf(nPtr, nmax,
2084 (i==0)?"%s@%s" : ",%s@%s",
2088 EncodedName = rfc2047encode(name, strlen(name));
2089 n = snprintf(nPtr, nmax,
2090 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
2091 EncodedName, user, node);
2096 n = snprintf(nPtr, nmax,
2097 (i==0)?"%s" : ",%s",
2098 &source[AddrPtr[i]]);
2104 ptr = (char*) malloc(EncodedMaxLen * 2);
2105 memcpy(ptr, Encoded, EncodedMaxLen);
2106 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
2107 free(Encoded), Encoded = ptr;
2109 i--; /* do it once more with properly lengthened buffer */
2112 for (i = 0; i < nColons; i++)
2113 source[--AddrPtr[i]] = ',';
2124 /* If the last item in a list of recipients was truncated to a partial address,
2125 * remove it completely in order to avoid choking libSieve
2127 void sanitize_truncated_recipient(char *str)
2130 if (num_tokens(str, ',') < 2) return;
2132 int len = strlen(str);
2133 if (len < 900) return;
2134 if (len > 998) str[998] = 0;
2136 char *cptr = strrchr(str, ',');
2139 char *lptr = strchr(cptr, '<');
2140 char *rptr = strchr(cptr, '>');
2142 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2148 void OutputCtdlMsgHeaders(
2149 struct CtdlMessage *TheMessage,
2150 int do_proto) /* do Citadel protocol responses? */
2155 char display_name[256];
2157 /* begin header processing loop for Citadel message format */
2158 safestrncpy(display_name, "<unknown>", sizeof display_name);
2159 if (!CM_IsEmpty(TheMessage, eAuthor)) {
2160 strcpy(buf, TheMessage->cm_fields[eAuthor]);
2161 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2162 safestrncpy(display_name, "****", sizeof display_name);
2164 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2165 safestrncpy(display_name, "anonymous", sizeof display_name);
2168 safestrncpy(display_name, buf, sizeof display_name);
2170 if ((is_room_aide())
2171 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2172 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2173 size_t tmp = strlen(display_name);
2174 snprintf(&display_name[tmp],
2175 sizeof display_name - tmp,
2180 /* Don't show Internet address for users on the
2181 * local Citadel network.
2184 if (!CM_IsEmpty(TheMessage, eNodeName) &&
2185 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
2190 /* Now spew the header fields in the order we like them. */
2191 for (i=0; i< NDiskFields; ++i) {
2193 Field = FieldOrder[i];
2194 if (Field != eMesageText) {
2195 if ( (!CM_IsEmpty(TheMessage, Field))
2196 && (msgkeys[Field] != NULL) ) {
2197 if ((Field == eenVelopeTo) ||
2198 (Field == eRecipient) ||
2199 (Field == eCarbonCopY)) {
2200 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
2202 if (Field == eAuthor) {
2203 if (do_proto) cprintf("%s=%s\n",
2207 else if ((Field == erFc822Addr) && (suppress_f)) {
2210 /* Masquerade display name if needed */
2212 if (do_proto) cprintf("%s=%s\n",
2214 TheMessage->cm_fields[Field]
2223 void OutputRFC822MsgHeaders(
2224 struct CtdlMessage *TheMessage,
2225 int flags, /* should the bessage be exported clean */
2227 char *mid, long sizeof_mid,
2228 char *suser, long sizeof_suser,
2229 char *luser, long sizeof_luser,
2230 char *fuser, long sizeof_fuser,
2231 char *snode, long sizeof_snode)
2233 char datestamp[100];
2234 int subject_found = 0;
2241 for (i = 0; i < 256; ++i) {
2242 if (TheMessage->cm_fields[i]) {
2243 mptr = mpptr = TheMessage->cm_fields[i];
2246 safestrncpy(luser, mptr, sizeof_luser);
2247 safestrncpy(suser, mptr, sizeof_suser);
2249 else if (i == 'Y') {
2250 if ((flags & QP_EADDR) != 0) {
2251 mptr = qp_encode_email_addrs(mptr);
2253 sanitize_truncated_recipient(mptr);
2254 cprintf("CC: %s%s", mptr, nl);
2256 else if (i == 'P') {
2257 cprintf("Return-Path: %s%s", mptr, nl);
2259 else if (i == eListID) {
2260 cprintf("List-ID: %s%s", mptr, nl);
2262 else if (i == 'V') {
2263 if ((flags & QP_EADDR) != 0)
2264 mptr = qp_encode_email_addrs(mptr);
2266 while ((*hptr != '\0') && isspace(*hptr))
2268 if (!IsEmptyStr(hptr))
2269 cprintf("Envelope-To: %s%s", hptr, nl);
2271 else if (i == 'U') {
2272 cprintf("Subject: %s%s", mptr, nl);
2276 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2277 else if (i == erFc822Addr)
2278 safestrncpy(fuser, mptr, sizeof_fuser);
2279 /* else if (i == 'O')
2280 cprintf("X-Citadel-Room: %s%s",
2283 safestrncpy(snode, mptr, sizeof_snode);
2286 if (haschar(mptr, '@') == 0)
2288 sanitize_truncated_recipient(mptr);
2289 cprintf("To: %s@%s", mptr, config.c_fqdn);
2294 if ((flags & QP_EADDR) != 0) {
2295 mptr = qp_encode_email_addrs(mptr);
2297 sanitize_truncated_recipient(mptr);
2298 cprintf("To: %s", mptr);
2302 else if (i == 'T') {
2303 datestring(datestamp, sizeof datestamp,
2304 atol(mptr), DATESTRING_RFC822);
2305 cprintf("Date: %s%s", datestamp, nl);
2307 else if (i == 'W') {
2308 cprintf("References: ");
2309 k = num_tokens(mptr, '|');
2310 for (j=0; j<k; ++j) {
2311 extract_token(buf, mptr, j, '|', sizeof buf);
2312 cprintf("<%s>", buf);
2321 else if (i == eReplyTo) {
2323 while ((*hptr != '\0') && isspace(*hptr))
2325 if (!IsEmptyStr(hptr))
2326 cprintf("Reply-To: %s%s", mptr, nl);
2332 if (subject_found == 0) {
2333 cprintf("Subject: (no subject)%s", nl);
2338 void Dump_RFC822HeadersBody(
2339 struct CtdlMessage *TheMessage,
2340 int headers_only, /* eschew the message body? */
2341 int flags, /* should the bessage be exported clean? */
2345 cit_uint8_t prev_ch;
2347 const char *StartOfText = StrBufNOTNULL;
2350 int nllen = strlen(nl);
2353 mptr = TheMessage->cm_fields[eMesageText];
2357 while (*mptr != '\0') {
2358 if (*mptr == '\r') {
2365 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2367 eoh = *(mptr+1) == '\n';
2371 StartOfText = strchr(StartOfText, '\n');
2372 StartOfText = strchr(StartOfText, '\n');
2375 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2376 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2377 ((headers_only != HEADERS_NONE) &&
2378 (headers_only != HEADERS_ONLY))
2380 if (*mptr == '\n') {
2381 memcpy(&outbuf[outlen], nl, nllen);
2383 outbuf[outlen] = '\0';
2386 outbuf[outlen++] = *mptr;
2390 if (flags & ESC_DOT)
2392 if ((prev_ch == '\n') &&
2394 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2396 outbuf[outlen++] = '.';
2401 if (outlen > 1000) {
2402 if (client_write(outbuf, outlen) == -1)
2404 struct CitContext *CCC = CC;
2405 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2412 client_write(outbuf, outlen);
2418 /* If the format type on disk is 1 (fixed-format), then we want
2419 * everything to be output completely literally ... regardless of
2420 * what message transfer format is in use.
2422 void DumpFormatFixed(
2423 struct CtdlMessage *TheMessage,
2424 int mode, /* how would you like that message? */
2431 int nllen = strlen (nl);
2434 mptr = TheMessage->cm_fields[eMesageText];
2436 if (mode == MT_MIME) {
2437 cprintf("Content-type: text/plain\n\n");
2441 while (ch = *mptr++, ch > 0) {
2445 if ((buflen > 250) && (!xlline)){
2449 while ((buflen > 0) &&
2450 (!isspace(buf[buflen])))
2456 mptr -= tbuflen - buflen;
2461 /* if we reach the outer bounds of our buffer,
2462 abort without respect what whe purge. */
2465 (buflen > SIZ - nllen - 2)))
2469 memcpy (&buf[buflen], nl, nllen);
2473 if (client_write(buf, buflen) == -1)
2475 struct CitContext *CCC = CC;
2476 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2488 if (!IsEmptyStr(buf))
2489 cprintf("%s%s", buf, nl);
2493 * Get a message off disk. (returns om_* values found in msgbase.h)
2495 int CtdlOutputPreLoadedMsg(
2496 struct CtdlMessage *TheMessage,
2497 int mode, /* how would you like that message? */
2498 int headers_only, /* eschew the message body? */
2499 int do_proto, /* do Citadel protocol responses? */
2500 int crlf, /* Use CRLF newlines instead of LF? */
2501 int flags /* should the bessage be exported clean? */
2503 struct CitContext *CCC = CC;
2506 const char *nl; /* newline string */
2509 /* Buffers needed for RFC822 translation. These are all filled
2510 * using functions that are bounds-checked, and therefore we can
2511 * make them substantially smaller than SIZ.
2519 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2520 ((TheMessage == NULL) ? "NULL" : "not null"),
2521 mode, headers_only, do_proto, crlf);
2523 strcpy(mid, "unknown");
2524 nl = (crlf ? "\r\n" : "\n");
2526 if (!is_valid_message(TheMessage)) {
2527 MSGM_syslog(LOG_ERR,
2528 "ERROR: invalid preloaded message for output\n");
2530 return(om_no_such_msg);
2533 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2534 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2536 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2537 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2540 /* Are we downloading a MIME component? */
2541 if (mode == MT_DOWNLOAD) {
2542 if (TheMessage->cm_format_type != FMT_RFC822) {
2544 cprintf("%d This is not a MIME message.\n",
2545 ERROR + ILLEGAL_VALUE);
2546 } else if (CCC->download_fp != NULL) {
2547 if (do_proto) cprintf(
2548 "%d You already have a download open.\n",
2549 ERROR + RESOURCE_BUSY);
2551 /* Parse the message text component */
2552 mptr = TheMessage->cm_fields[eMesageText];
2553 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2554 /* If there's no file open by this time, the requested
2555 * section wasn't found, so print an error
2557 if (CCC->download_fp == NULL) {
2558 if (do_proto) cprintf(
2559 "%d Section %s not found.\n",
2560 ERROR + FILE_NOT_FOUND,
2561 CCC->download_desired_section);
2564 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2567 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2568 * in a single server operation instead of opening a download file.
2570 if (mode == MT_SPEW_SECTION) {
2571 if (TheMessage->cm_format_type != FMT_RFC822) {
2573 cprintf("%d This is not a MIME message.\n",
2574 ERROR + ILLEGAL_VALUE);
2576 /* Parse the message text component */
2579 mptr = TheMessage->cm_fields[eMesageText];
2580 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2581 /* If section wasn't found, print an error
2584 if (do_proto) cprintf(
2585 "%d Section %s not found.\n",
2586 ERROR + FILE_NOT_FOUND,
2587 CCC->download_desired_section);
2590 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2593 /* now for the user-mode message reading loops */
2594 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2596 /* Does the caller want to skip the headers? */
2597 if (headers_only == HEADERS_NONE) goto START_TEXT;
2599 /* Tell the client which format type we're using. */
2600 if ( (mode == MT_CITADEL) && (do_proto) ) {
2601 cprintf("type=%d\n", TheMessage->cm_format_type);
2604 /* nhdr=yes means that we're only displaying headers, no body */
2605 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2606 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2609 cprintf("nhdr=yes\n");
2612 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2613 OutputCtdlMsgHeaders(TheMessage, do_proto);
2616 /* begin header processing loop for RFC822 transfer format */
2620 strcpy(snode, NODENAME);
2621 if (mode == MT_RFC822)
2622 OutputRFC822MsgHeaders(
2627 suser, sizeof(suser),
2628 luser, sizeof(luser),
2629 fuser, sizeof(fuser),
2630 snode, sizeof(snode)
2634 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2635 suser[i] = tolower(suser[i]);
2636 if (!isalnum(suser[i])) suser[i]='_';
2639 if (mode == MT_RFC822) {
2640 if (!strcasecmp(snode, NODENAME)) {
2641 safestrncpy(snode, FQDN, sizeof snode);
2644 /* Construct a fun message id */
2645 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2646 if (strchr(mid, '@')==NULL) {
2647 cprintf("@%s", snode);
2651 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2652 cprintf("From: \"----\" <x@x.org>%s", nl);
2654 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2655 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2657 else if (!IsEmptyStr(fuser)) {
2658 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2661 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2664 /* Blank line signifying RFC822 end-of-headers */
2665 if (TheMessage->cm_format_type != FMT_RFC822) {
2670 /* end header processing loop ... at this point, we're in the text */
2672 if (headers_only == HEADERS_FAST) goto DONE;
2674 /* Tell the client about the MIME parts in this message */
2675 if (TheMessage->cm_format_type == FMT_RFC822) {
2676 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2677 mptr = TheMessage->cm_fields[eMesageText];
2678 memset(&ma, 0, sizeof(struct ma_info));
2679 mime_parser(mptr, NULL,
2680 (do_proto ? *list_this_part : NULL),
2681 (do_proto ? *list_this_pref : NULL),
2682 (do_proto ? *list_this_suff : NULL),
2685 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2686 Dump_RFC822HeadersBody(
2695 if (headers_only == HEADERS_ONLY) {
2699 /* signify start of msg text */
2700 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2701 if (do_proto) cprintf("text\n");
2704 if (TheMessage->cm_format_type == FMT_FIXED)
2707 mode, /* how would you like that message? */
2710 /* If the message on disk is format 0 (Citadel vari-format), we
2711 * output using the formatter at 80 columns. This is the final output
2712 * form if the transfer format is RFC822, but if the transfer format
2713 * is Citadel proprietary, it'll still work, because the indentation
2714 * for new paragraphs is correct and the client will reformat the
2715 * message to the reader's screen width.
2717 if (TheMessage->cm_format_type == FMT_CITADEL) {
2718 mptr = TheMessage->cm_fields[eMesageText];
2720 if (mode == MT_MIME) {
2721 cprintf("Content-type: text/x-citadel-variformat\n\n");
2726 /* If the message on disk is format 4 (MIME), we've gotta hand it
2727 * off to the MIME parser. The client has already been told that
2728 * this message is format 1 (fixed format), so the callback function
2729 * we use will display those parts as-is.
2731 if (TheMessage->cm_format_type == FMT_RFC822) {
2732 memset(&ma, 0, sizeof(struct ma_info));
2734 if (mode == MT_MIME) {
2735 ma.use_fo_hooks = 0;
2736 strcpy(ma.chosen_part, "1");
2737 ma.chosen_pref = 9999;
2738 ma.dont_decode = CCC->msg4_dont_decode;
2739 mime_parser(mptr, NULL,
2740 *choose_preferred, *fixed_output_pre,
2741 *fixed_output_post, (void *)&ma, 1);
2742 mime_parser(mptr, NULL,
2743 *output_preferred, NULL, NULL, (void *)&ma, 1);
2746 ma.use_fo_hooks = 1;
2747 mime_parser(mptr, NULL,
2748 *fixed_output, *fixed_output_pre,
2749 *fixed_output_post, (void *)&ma, 0);
2754 DONE: /* now we're done */
2755 if (do_proto) cprintf("000\n");
2761 * display a message (mode 0 - Citadel proprietary)
2763 void cmd_msg0(char *cmdbuf)
2766 int headers_only = HEADERS_ALL;
2768 msgid = extract_long(cmdbuf, 0);
2769 headers_only = extract_int(cmdbuf, 1);
2771 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2777 * display a message (mode 2 - RFC822)
2779 void cmd_msg2(char *cmdbuf)
2782 int headers_only = HEADERS_ALL;
2784 msgid = extract_long(cmdbuf, 0);
2785 headers_only = extract_int(cmdbuf, 1);
2787 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2793 * display a message (mode 3 - IGnet raw format - internal programs only)
2795 void cmd_msg3(char *cmdbuf)
2798 struct CtdlMessage *msg = NULL;
2801 if (CC->internal_pgm == 0) {
2802 cprintf("%d This command is for internal programs only.\n",
2803 ERROR + HIGHER_ACCESS_REQUIRED);
2807 msgnum = extract_long(cmdbuf, 0);
2808 msg = CtdlFetchMessage(msgnum, 1);
2810 cprintf("%d Message %ld not found.\n",
2811 ERROR + MESSAGE_NOT_FOUND, msgnum);
2815 serialize_message(&smr, msg);
2816 CtdlFreeMessage(msg);
2819 cprintf("%d Unable to serialize message\n",
2820 ERROR + INTERNAL_ERROR);
2824 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2825 client_write((char *)smr.ser, (int)smr.len);
2832 * Display a message using MIME content types
2834 void cmd_msg4(char *cmdbuf)
2839 msgid = extract_long(cmdbuf, 0);
2840 extract_token(section, cmdbuf, 1, '|', sizeof section);
2841 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2847 * Client tells us its preferred message format(s)
2849 void cmd_msgp(char *cmdbuf)
2851 if (!strcasecmp(cmdbuf, "dont_decode")) {
2852 CC->msg4_dont_decode = 1;
2853 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2856 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2857 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2863 * Open a component of a MIME message as a download file
2865 void cmd_opna(char *cmdbuf)
2868 char desired_section[128];
2870 msgid = extract_long(cmdbuf, 0);
2871 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2872 safestrncpy(CC->download_desired_section, desired_section,
2873 sizeof CC->download_desired_section);
2874 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2879 * Open a component of a MIME message and transmit it all at once
2881 void cmd_dlat(char *cmdbuf)
2884 char desired_section[128];
2886 msgid = extract_long(cmdbuf, 0);
2887 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2888 safestrncpy(CC->download_desired_section, desired_section,
2889 sizeof CC->download_desired_section);
2890 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2895 * Save one or more message pointers into a specified room
2896 * (Returns 0 for success, nonzero for failure)
2897 * roomname may be NULL to use the current room
2899 * Note that the 'supplied_msg' field may be set to NULL, in which case
2900 * the message will be fetched from disk, by number, if we need to perform
2901 * replication checks. This adds an additional database read, so if the
2902 * caller already has the message in memory then it should be supplied. (Obviously
2903 * this mode of operation only works if we're saving a single message.)
2905 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2906 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2908 struct CitContext *CCC = CC;
2910 char hold_rm[ROOMNAMELEN];
2911 struct cdbdata *cdbfr;
2914 long highest_msg = 0L;
2917 struct CtdlMessage *msg = NULL;
2919 long *msgs_to_be_merged = NULL;
2920 int num_msgs_to_be_merged = 0;
2922 MSG_syslog(LOG_DEBUG,
2923 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2924 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2927 strcpy(hold_rm, CCC->room.QRname);
2930 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2931 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2932 if (num_newmsgs > 1) supplied_msg = NULL;
2934 /* Now the regular stuff */
2935 if (CtdlGetRoomLock(&CCC->room,
2936 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2938 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2939 return(ERROR + ROOM_NOT_FOUND);
2943 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2944 num_msgs_to_be_merged = 0;
2947 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2948 if (cdbfr == NULL) {
2952 msglist = (long *) cdbfr->ptr;
2953 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2954 num_msgs = cdbfr->len / sizeof(long);
2959 /* Create a list of msgid's which were supplied by the caller, but do
2960 * not already exist in the target room. It is absolutely taboo to
2961 * have more than one reference to the same message in a room.
2963 for (i=0; i<num_newmsgs; ++i) {
2965 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2966 if (msglist[j] == newmsgidlist[i]) {
2971 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2975 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2978 * Now merge the new messages
2980 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2981 if (msglist == NULL) {
2982 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2983 free(msgs_to_be_merged);
2984 return (ERROR + INTERNAL_ERROR);
2986 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2987 num_msgs += num_msgs_to_be_merged;
2989 /* Sort the message list, so all the msgid's are in order */
2990 num_msgs = sort_msglist(msglist, num_msgs);
2992 /* Determine the highest message number */
2993 highest_msg = msglist[num_msgs - 1];
2995 /* Write it back to disk. */
2996 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2997 msglist, (int)(num_msgs * sizeof(long)));
2999 /* Free up the memory we used. */
3002 /* Update the highest-message pointer and unlock the room. */
3003 CCC->room.QRhighest = highest_msg;
3004 CtdlPutRoomLock(&CCC->room);
3006 /* Perform replication checks if necessary */
3007 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
3008 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
3010 for (i=0; i<num_msgs_to_be_merged; ++i) {
3011 msgid = msgs_to_be_merged[i];
3013 if (supplied_msg != NULL) {
3017 msg = CtdlFetchMessage(msgid, 0);
3021 ReplicationChecks(msg);
3023 /* If the message has an Exclusive ID, index that... */
3024 if (!CM_IsEmpty(msg, eExclusiveID)) {
3025 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
3028 /* Free up the memory we may have allocated */
3029 if (msg != supplied_msg) {
3030 CtdlFreeMessage(msg);
3038 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
3041 /* Submit this room for processing by hooks */
3042 PerformRoomHooks(&CCC->room);
3044 /* Go back to the room we were in before we wandered here... */
3045 CtdlGetRoom(&CCC->room, hold_rm);
3047 /* Bump the reference count for all messages which were merged */
3048 if (!suppress_refcount_adj) {
3049 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
3052 /* Free up memory... */
3053 if (msgs_to_be_merged != NULL) {
3054 free(msgs_to_be_merged);
3057 /* Return success. */
3063 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
3066 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
3067 int do_repl_check, struct CtdlMessage *supplied_msg)
3069 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
3076 * Message base operation to save a new message to the message store
3077 * (returns new message number)
3079 * This is the back end for CtdlSubmitMsg() and should not be directly
3080 * called by server-side modules.
3083 long send_message(struct CtdlMessage *msg) {
3084 struct CitContext *CCC = CC;
3093 /* Get a new message number */
3094 newmsgid = get_new_message_number();
3095 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
3096 (long unsigned int) time(NULL),
3097 (long unsigned int) newmsgid,
3101 /* Generate an ID if we don't have one already */
3102 if (CM_IsEmpty(msg, emessageId)) {
3103 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
3106 /* If the message is big, set its body aside for storage elsewhere */
3107 if (!CM_IsEmpty(msg, eMesageText)) {
3108 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
3110 holdM = msg->cm_fields[eMesageText];
3111 msg->cm_fields[eMesageText] = NULL;
3115 /* Serialize our data structure for storage in the database */
3116 serialize_message(&smr, msg);
3119 msg->cm_fields[eMesageText] = holdM;
3123 cprintf("%d Unable to serialize message\n",
3124 ERROR + INTERNAL_ERROR);
3128 /* Write our little bundle of joy into the message base */
3129 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3130 smr.ser, smr.len) < 0) {
3131 MSGM_syslog(LOG_ERR, "Can't store message\n");
3135 cdb_store(CDB_BIGMSGS,
3145 /* Free the memory we used for the serialized message */
3148 /* Return the *local* message ID to the caller
3149 * (even if we're storing an incoming network message)
3157 * Serialize a struct CtdlMessage into the format used on disk and network.
3159 * This function loads up a "struct ser_ret" (defined in server.h) which
3160 * contains the length of the serialized message and a pointer to the
3161 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3163 void serialize_message(struct ser_ret *ret, /* return values */
3164 struct CtdlMessage *msg) /* unserialized msg */
3166 struct CitContext *CCC = CC;
3167 size_t wlen, fieldlen;
3169 long lengths[NDiskFields];
3171 memset(lengths, 0, sizeof(lengths));
3174 * Check for valid message format
3176 if (is_valid_message(msg) == 0) {
3177 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3184 for (i=0; i < NDiskFields; ++i)
3185 if (msg->cm_fields[FieldOrder[i]] != NULL)
3187 lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
3188 ret->len += lengths[i] + 2;
3191 ret->ser = malloc(ret->len);
3192 if (ret->ser == NULL) {
3193 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3194 (long)ret->len, strerror(errno));
3201 ret->ser[1] = msg->cm_anon_type;
3202 ret->ser[2] = msg->cm_format_type;
3205 for (i=0; i < NDiskFields; ++i)
3206 if (msg->cm_fields[FieldOrder[i]] != NULL)
3208 fieldlen = lengths[i];
3209 ret->ser[wlen++] = (char)FieldOrder[i];
3211 memcpy(&ret->ser[wlen],
3212 msg->cm_fields[FieldOrder[i]],
3215 wlen = wlen + fieldlen + 1;
3218 if (ret->len != wlen) {
3219 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3220 (long)ret->len, (long)wlen);
3228 * Check to see if any messages already exist in the current room which
3229 * carry the same Exclusive ID as this one. If any are found, delete them.
3231 void ReplicationChecks(struct CtdlMessage *msg) {
3232 struct CitContext *CCC = CC;
3233 long old_msgnum = (-1L);
3235 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3237 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3240 /* No exclusive id? Don't do anything. */
3241 if (msg == NULL) return;
3242 if (CM_IsEmpty(msg, eExclusiveID)) return;
3244 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3245 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
3247 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
3248 if (old_msgnum > 0L) {
3249 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3250 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3257 * Save a message to disk and submit it into the delivery system.
3259 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3260 struct recptypes *recps, /* recipients (if mail) */
3261 const char *force, /* force a particular room? */
3262 int flags /* should the message be exported clean? */
3265 char submit_filename[128];
3266 char hold_rm[ROOMNAMELEN];
3267 char actual_rm[ROOMNAMELEN];
3268 char force_room[ROOMNAMELEN];
3269 char content_type[SIZ]; /* We have to learn this */
3270 char recipient[SIZ];
3273 const char *mptr = NULL;
3274 struct ctdluser userbuf;
3276 struct MetaData smi;
3277 FILE *network_fp = NULL;
3278 static int seqnum = 1;
3279 struct CtdlMessage *imsg = NULL;
3281 size_t instr_alloc = 0;
3283 char *hold_R, *hold_D;
3284 char *collected_addresses = NULL;
3285 struct addresses_to_be_filed *aptr = NULL;
3286 StrBuf *saved_rfc822_version = NULL;
3287 int qualified_for_journaling = 0;
3288 CitContext *CCC = MyContext();
3289 char bounce_to[1024] = "";
3292 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3293 if (is_valid_message(msg) == 0) return(-1); /* self check */
3295 /* If this message has no timestamp, we take the liberty of
3296 * giving it one, right now.
3298 if (CM_IsEmpty(msg, eTimestamp)) {
3299 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
3302 /* If this message has no path, we generate one.
3304 if (CM_IsEmpty(msg, eMessagePath)) {
3305 if (!CM_IsEmpty(msg, eAuthor)) {
3306 CM_CopyField(msg, eMessagePath, eAuthor);
3307 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
3308 if (isspace(msg->cm_fields[eMessagePath][a])) {
3309 msg->cm_fields[eMessagePath][a] = ' ';
3314 CM_SetField(msg, eMessagePath, HKEY("unknown"));
3318 if (force == NULL) {
3319 force_room[0] = '\0';
3322 strcpy(force_room, force);
3325 /* Learn about what's inside, because it's what's inside that counts */
3326 if (CM_IsEmpty(msg, eMesageText)) {
3327 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3331 switch (msg->cm_format_type) {
3333 strcpy(content_type, "text/x-citadel-variformat");
3336 strcpy(content_type, "text/plain");
3339 strcpy(content_type, "text/plain");
3340 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3343 safestrncpy(content_type, &mptr[13], sizeof content_type);
3344 striplt(content_type);
3345 aptr = content_type;
3346 while (!IsEmptyStr(aptr)) {
3358 /* Goto the correct room */
3359 room = (recps) ? CCC->room.QRname : SENTITEMS;
3360 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3361 strcpy(hold_rm, CCC->room.QRname);
3362 strcpy(actual_rm, CCC->room.QRname);
3363 if (recps != NULL) {
3364 strcpy(actual_rm, SENTITEMS);
3367 /* If the user is a twit, move to the twit room for posting */
3369 if (CCC->user.axlevel == AxProbU) {
3370 strcpy(hold_rm, actual_rm);
3371 strcpy(actual_rm, config.c_twitroom);
3372 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3376 /* ...or if this message is destined for Aide> then go there. */
3377 if (!IsEmptyStr(force_room)) {
3378 strcpy(actual_rm, force_room);
3381 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3382 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3383 /* CtdlGetRoom(&CCC->room, actual_rm); */
3384 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3388 * If this message has no O (room) field, generate one.
3390 if (CM_IsEmpty(msg, eOriginalRoom)) {
3391 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3394 /* Perform "before save" hooks (aborting if any return nonzero) */
3395 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3396 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3399 * If this message has an Exclusive ID, and the room is replication
3400 * checking enabled, then do replication checks.
3402 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3403 ReplicationChecks(msg);
3406 /* Save it to disk */
3407 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3408 newmsgid = send_message(msg);
3409 if (newmsgid <= 0L) return(-5);
3411 /* Write a supplemental message info record. This doesn't have to
3412 * be a critical section because nobody else knows about this message
3415 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3416 memset(&smi, 0, sizeof(struct MetaData));
3417 smi.meta_msgnum = newmsgid;
3418 smi.meta_refcount = 0;
3419 safestrncpy(smi.meta_content_type, content_type,
3420 sizeof smi.meta_content_type);
3423 * Measure how big this message will be when rendered as RFC822.
3424 * We do this for two reasons:
3425 * 1. We need the RFC822 length for the new metadata record, so the
3426 * POP and IMAP services don't have to calculate message lengths
3427 * while the user is waiting (multiplied by potentially hundreds
3428 * or thousands of messages).
3429 * 2. If journaling is enabled, we will need an RFC822 version of the
3430 * message to attach to the journalized copy.
3432 if (CCC->redirect_buffer != NULL) {
3433 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3436 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3437 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3438 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3439 saved_rfc822_version = CCC->redirect_buffer;
3440 CCC->redirect_buffer = NULL;
3444 /* Now figure out where to store the pointers */
3445 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3447 /* If this is being done by the networker delivering a private
3448 * message, we want to BYPASS saving the sender's copy (because there
3449 * is no local sender; it would otherwise go to the Trashcan).
3451 if ((!CCC->internal_pgm) || (recps == NULL)) {
3452 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3453 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3454 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3458 /* For internet mail, drop a copy in the outbound queue room */
3459 if ((recps != NULL) && (recps->num_internet > 0)) {
3460 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3463 /* If other rooms are specified, drop them there too. */
3464 if ((recps != NULL) && (recps->num_room > 0))
3465 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3466 extract_token(recipient, recps->recp_room, i,
3467 '|', sizeof recipient);
3468 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3469 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3472 /* Bump this user's messages posted counter. */
3473 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3474 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3475 CCC->user.posted = CCC->user.posted + 1;
3476 CtdlPutUserLock(&CCC->user);
3478 /* Decide where bounces need to be delivered */
3479 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3480 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3482 else if (CCC->logged_in) {
3483 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3486 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3489 /* If this is private, local mail, make a copy in the
3490 * recipient's mailbox and bump the reference count.
3492 if ((recps != NULL) && (recps->num_local > 0))
3493 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3495 recipientlen = extract_token(recipient,
3496 recps->recp_local, i,
3497 '|', sizeof recipient);
3498 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3500 if (CtdlGetUser(&userbuf, recipient) == 0) {
3501 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3502 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3503 CtdlBumpNewMailCounter(userbuf.usernum);
3504 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3505 /* Generate a instruction message for the Funambol notification
3506 * server, in the same style as the SMTP queue
3510 instr = malloc(instr_alloc);
3511 instrlen = snprintf(
3513 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3517 (long)time(NULL), //todo: time() is expensive!
3521 imsg = malloc(sizeof(struct CtdlMessage));
3522 memset(imsg, 0, sizeof(struct CtdlMessage));
3523 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3524 imsg->cm_anon_type = MES_NORMAL;
3525 imsg->cm_format_type = FMT_RFC822;
3526 CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
3527 CM_SetField(imsg, eAuthor, HKEY("Citadel"));
3528 CM_SetField(imsg, eJournal, HKEY("do not journal"));
3529 CM_SetAsField(imsg, eMesageText, &instr, instrlen);
3530 CM_SetField(imsg, eExtnotify, recipient, recipientlen);
3531 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3532 CtdlFreeMessage(imsg);
3536 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3537 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3541 /* Perform "after save" hooks */
3542 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3544 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
3545 PerformMessageHooks(msg, EVT_AFTERSAVE);
3546 CM_FlushField(msg, eVltMsgNum);
3548 /* For IGnet mail, we have to save a new copy into the spooler for
3549 * each recipient, with the R and D fields set to the recipient and
3550 * destination-node. This has two ugly side effects: all other
3551 * recipients end up being unlisted in this recipient's copy of the
3552 * message, and it has to deliver multiple messages to the same
3553 * node. We'll revisit this again in a year or so when everyone has
3554 * a network spool receiver that can handle the new style messages.
3556 if ((recps != NULL) && (recps->num_ignet > 0))
3557 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3558 extract_token(recipient, recps->recp_ignet, i,
3559 '|', sizeof recipient);
3561 hold_R = msg->cm_fields[eRecipient];
3562 hold_D = msg->cm_fields[eDestination];
3563 msg->cm_fields[eRecipient] = malloc(SIZ);
3564 msg->cm_fields[eDestination] = malloc(128);
3565 extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3566 extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3568 serialize_message(&smr, msg);
3570 snprintf(submit_filename, sizeof submit_filename,
3571 "%s/netmail.%04lx.%04x.%04x",
3573 (long) getpid(), CCC->cs_pid, ++seqnum);
3574 network_fp = fopen(submit_filename, "wb+");
3575 if (network_fp != NULL) {
3576 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3578 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3586 free(msg->cm_fields[eRecipient]);
3587 free(msg->cm_fields[eDestination]);
3588 msg->cm_fields[eRecipient] = hold_R;
3589 msg->cm_fields[eDestination] = hold_D;
3592 /* Go back to the room we started from */
3593 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3594 if (strcasecmp(hold_rm, CCC->room.QRname))
3595 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3597 /* For internet mail, generate delivery instructions.
3598 * Yes, this is recursive. Deal with it. Infinite recursion does
3599 * not happen because the delivery instructions message does not
3600 * contain a recipient.
3602 if ((recps != NULL) && (recps->num_internet > 0)) {
3603 StrBuf *SpoolMsg = NewStrBuf();
3606 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3608 StrBufPrintf(SpoolMsg,
3609 "Content-type: "SPOOLMIME"\n"
3618 if (recps->envelope_from != NULL) {
3619 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3620 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3621 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3623 if (recps->sending_room != NULL) {
3624 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3625 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3626 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3629 nTokens = num_tokens(recps->recp_internet, '|');
3630 for (i = 0; i < nTokens; i++) {
3632 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3634 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3635 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3636 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3640 imsg = malloc(sizeof(struct CtdlMessage));
3641 memset(imsg, 0, sizeof(struct CtdlMessage));
3642 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3643 imsg->cm_anon_type = MES_NORMAL;
3644 imsg->cm_format_type = FMT_RFC822;
3645 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3646 imsg->cm_fields[eAuthor] = strdup("Citadel");
3647 imsg->cm_fields[eJournal] = strdup("do not journal");
3648 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3649 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3650 CtdlFreeMessage(imsg);
3654 * Any addresses to harvest for someone's address book?
3656 if ( (CCC->logged_in) && (recps != NULL) ) {
3657 collected_addresses = harvest_collected_addresses(msg);
3660 if (collected_addresses != NULL) {
3661 aptr = (struct addresses_to_be_filed *)
3662 malloc(sizeof(struct addresses_to_be_filed));
3663 CtdlMailboxName(actual_rm, sizeof actual_rm,
3664 &CCC->user, USERCONTACTSROOM);
3665 aptr->roomname = strdup(actual_rm);
3666 aptr->collected_addresses = collected_addresses;
3667 begin_critical_section(S_ATBF);
3670 end_critical_section(S_ATBF);
3674 * Determine whether this message qualifies for journaling.
3676 if (!CM_IsEmpty(msg, eJournal)) {
3677 qualified_for_journaling = 0;
3680 if (recps == NULL) {
3681 qualified_for_journaling = config.c_journal_pubmsgs;
3683 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3684 qualified_for_journaling = config.c_journal_email;
3687 qualified_for_journaling = config.c_journal_pubmsgs;
3692 * Do we have to perform journaling? If so, hand off the saved
3693 * RFC822 version will be handed off to the journaler for background
3694 * submit. Otherwise, we have to free the memory ourselves.
3696 if (saved_rfc822_version != NULL) {
3697 if (qualified_for_journaling) {
3698 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3701 FreeStrBuf(&saved_rfc822_version);
3711 * Convenience function for generating small administrative messages.
3713 void quickie_message(const char *from,
3714 const char *fromaddr,
3719 const char *subject)
3721 struct CtdlMessage *msg;
3722 struct recptypes *recp = NULL;
3724 msg = malloc(sizeof(struct CtdlMessage));
3725 memset(msg, 0, sizeof(struct CtdlMessage));
3726 msg->cm_magic = CTDLMESSAGE_MAGIC;
3727 msg->cm_anon_type = MES_NORMAL;
3728 msg->cm_format_type = format_type;
3731 msg->cm_fields[eAuthor] = strdup(from);
3733 else if (fromaddr != NULL) {
3734 msg->cm_fields[eAuthor] = strdup(fromaddr);
3735 if (strchr(msg->cm_fields[eAuthor], '@')) {
3736 *strchr(msg->cm_fields[eAuthor], '@') = 0;
3740 msg->cm_fields[eAuthor] = strdup("Citadel");
3743 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3744 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3745 msg->cm_fields[eNodeName] = strdup(NODENAME);
3747 msg->cm_fields[eRecipient] = strdup(to);
3748 recp = validate_recipients(to, NULL, 0);
3750 if (subject != NULL) {
3751 msg->cm_fields[eMsgSubject] = strdup(subject);
3753 msg->cm_fields[eMesageText] = strdup(text);
3755 CtdlSubmitMsg(msg, recp, room, 0);
3756 CtdlFreeMessage(msg);
3757 if (recp != NULL) free_recipients(recp);
3760 void flood_protect_quickie_message(const char *from,
3761 const char *fromaddr,
3766 const char *subject,
3768 const char **CritStr,
3775 u_char rawdigest[MD5_DIGEST_LEN];
3776 struct MD5Context md5context;
3780 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3782 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3783 MD5Init(&md5context);
3785 for (i = 0; i < nCriterions; i++)
3786 MD5Update(&md5context,
3787 (const unsigned char*)CritStr[i], CritStrLen[i]);
3788 MD5Update(&md5context,
3789 (const unsigned char*)timestamp, tslen);
3790 MD5Final(rawdigest, &md5context);
3792 guid = NewStrBufPlain(NULL,
3793 MD5_DIGEST_LEN * 2 + 12);
3794 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3795 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3796 if (StrLength(guid) > 40)
3797 StrBufCutAt(guid, 40, NULL);
3799 if (CheckIfAlreadySeen("FPAideMessage",
3808 /* yes, we did. flood protection kicks in. */
3810 "not sending message again\n");
3814 /* no, this message isn't sent recently; go ahead. */
3815 quickie_message(from,
3826 * Back end function used by CtdlMakeMessage() and similar functions
3828 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3830 size_t maxlen, /* maximum message length */
3831 StrBuf *exist, /* if non-null, append to it;
3832 exist is ALWAYS freed */
3833 int crlf, /* CRLF newlines instead of LF */
3834 int *sock /* socket handle or 0 for this session's client socket */
3843 LineBuf = NewStrBufPlain(NULL, SIZ);
3844 if (exist == NULL) {
3845 Message = NewStrBufPlain(NULL, 4 * SIZ);
3848 Message = NewStrBufDup(exist);
3851 /* Do we need to change leading ".." to "." for SMTP escaping? */
3852 if ((tlen == 1) && (*terminator == '.')) {
3856 /* read in the lines of message text one by one */
3859 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3864 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3866 if ((StrLength(LineBuf) == tlen) &&
3867 (!strcmp(ChrPtr(LineBuf), terminator)))
3870 if ( (!flushing) && (!finished) ) {
3872 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3875 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3878 /* Unescape SMTP-style input of two dots at the beginning of the line */
3880 (StrLength(LineBuf) == 2) &&
3881 (!strcmp(ChrPtr(LineBuf), "..")))
3883 StrBufCutLeft(LineBuf, 1);
3886 StrBufAppendBuf(Message, LineBuf, 0);
3889 /* if we've hit the max msg length, flush the rest */
3890 if (StrLength(Message) >= maxlen) flushing = 1;
3892 } while (!finished);
3893 FreeStrBuf(&LineBuf);
3897 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3901 FreeStrBuf(&(*Msg)->MsgBuf);
3907 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3909 size_t maxlen, /* maximum message length */
3910 size_t expectlen, /* if we expect a message, how long should it be? */
3911 StrBuf *exist, /* if non-null, append to it;
3912 exist is ALWAYS freed */
3913 long eLen, /* length of exist */
3914 int crlf /* CRLF newlines instead of LF */
3917 ReadAsyncMsg *NewMsg;
3919 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3920 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3922 if (exist == NULL) {
3925 if (expectlen == 0) {
3929 len = expectlen + 10;
3931 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3934 NewMsg->MsgBuf = NewStrBufDup(exist);
3936 /* Do we need to change leading ".." to "." for SMTP escaping? */
3937 if ((tlen == 1) && (*terminator == '.')) {
3941 NewMsg->terminator = terminator;
3942 NewMsg->tlen = tlen;
3944 NewMsg->maxlen = maxlen;
3946 NewMsg->crlf = crlf;
3952 * Back end function used by CtdlMakeMessage() and similar functions
3954 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3956 ReadAsyncMsg *ReadMsg;
3957 int MsgFinished = 0;
3958 eReadState Finished = eMustReadMore;
3963 const char *pch = ChrPtr(IO->SendBuf.Buf);
3964 const char *pchh = IO->SendBuf.ReadWritePointer;
3970 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3971 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3972 ((CitContext*)(IO->CitContext))->ServiceName,
3975 fd = fopen(fn, "a+");
3978 ReadMsg = IO->ReadMsg;
3980 /* read in the lines of message text one by one */
3982 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3985 case eMustReadMore: /// read new from socket...
3987 if (IO->RecvBuf.ReadWritePointer != NULL) {
3988 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3989 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3991 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3995 fprintf(fd, "BufferEmpty! \n");
4001 case eBufferNotEmpty: /* shouldn't happen... */
4002 case eReadSuccess: /// done for now...
4004 case eReadFail: /// WHUT?
4010 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
4011 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
4014 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
4017 else if (!ReadMsg->flushing) {
4020 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
4023 /* Unescape SMTP-style input of two dots at the beginning of the line */
4024 if ((ReadMsg->dodot) &&
4025 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
4026 (!strcmp(ChrPtr(IO->IOBuf), "..")))
4029 fprintf(fd, "UnEscaped!\n");
4031 StrBufCutLeft(IO->IOBuf, 1);
4034 if (ReadMsg->crlf) {
4035 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
4038 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
4041 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
4044 /* if we've hit the max msg length, flush the rest */
4045 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
4047 } while (!MsgFinished);
4050 fprintf(fd, "Done with reading; %s.\n, ",
4051 (MsgFinished)?"Message Finished": "FAILED");
4055 return eReadSuccess;
4062 * Back end function used by CtdlMakeMessage() and similar functions
4064 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
4066 size_t maxlen, /* maximum message length */
4067 StrBuf *exist, /* if non-null, append to it;
4068 exist is ALWAYS freed */
4069 int crlf, /* CRLF newlines instead of LF */
4070 int *sock /* socket handle or 0 for this session's client socket */
4075 Message = CtdlReadMessageBodyBuf(terminator,
4081 if (Message == NULL)
4084 return SmashStrBuf(&Message);
4089 * Build a binary message to be saved on disk.
4090 * (NOTE: if you supply 'preformatted_text', the buffer you give it
4091 * will become part of the message. This means you are no longer
4092 * responsible for managing that memory -- it will be freed along with
4093 * the rest of the fields when CtdlFreeMessage() is called.)
4096 struct CtdlMessage *CtdlMakeMessage(
4097 struct ctdluser *author, /* author's user structure */
4098 char *recipient, /* NULL if it's not mail */
4099 char *recp_cc, /* NULL if it's not mail */
4100 char *room, /* room where it's going */
4101 int type, /* see MES_ types in header file */
4102 int format_type, /* variformat, plain text, MIME... */
4103 char *fake_name, /* who we're masquerading as */
4104 char *my_email, /* which of my email addresses to use (empty is ok) */
4105 char *subject, /* Subject (optional) */
4106 char *supplied_euid, /* ...or NULL if this is irrelevant */
4107 char *preformatted_text, /* ...or NULL to read text from client */
4108 char *references /* Thread references */
4110 char dest_node[256];
4112 struct CtdlMessage *msg;
4114 StrBuf *FakeEncAuthor = NULL;
4116 msg = malloc(sizeof(struct CtdlMessage));
4117 memset(msg, 0, sizeof(struct CtdlMessage));
4118 msg->cm_magic = CTDLMESSAGE_MAGIC;
4119 msg->cm_anon_type = type;
4120 msg->cm_format_type = format_type;
4122 /* Don't confuse the poor folks if it's not routed mail. */
4123 strcpy(dest_node, "");
4125 if (recipient != NULL) striplt(recipient);
4126 if (recp_cc != NULL) striplt(recp_cc);
4128 /* Path or Return-Path */
4129 if (my_email == NULL) my_email = "";
4131 if (!IsEmptyStr(my_email)) {
4132 msg->cm_fields[eMessagePath] = strdup(my_email);
4135 snprintf(buf, sizeof buf, "%s", author->fullname);
4136 msg->cm_fields[eMessagePath] = strdup(buf);
4138 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
4140 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
4141 msg->cm_fields[eTimestamp] = strdup(buf);
4143 if ((fake_name != NULL) && (fake_name[0])) { /* author */
4144 FakeAuthor = NewStrBufPlain (fake_name, -1);
4147 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4149 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4150 msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
4151 FreeStrBuf(&FakeAuthor);
4153 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4154 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
4157 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
4160 msg->cm_fields[eNodeName] = strdup(NODENAME); /* nodename */
4161 msg->cm_fields[eHumanNode] = strdup(HUMANNODE); /* hnodename */
4163 if ((recipient != NULL) && (recipient[0] != 0)) {
4164 msg->cm_fields[eRecipient] = strdup(recipient);
4166 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4167 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
4169 if (dest_node[0] != 0) {
4170 msg->cm_fields[eDestination] = strdup(dest_node);
4173 if (!IsEmptyStr(my_email)) {
4174 msg->cm_fields[erFc822Addr] = strdup(my_email);
4176 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4177 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
4180 if (subject != NULL) {
4183 length = strlen(subject);
4189 while ((subject[i] != '\0') &&
4190 (IsAscii = isascii(subject[i]) != 0 ))
4193 msg->cm_fields[eMsgSubject] = strdup(subject);
4194 else /* ok, we've got utf8 in the string. */
4196 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
4202 if (supplied_euid != NULL) {
4203 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
4206 if ((references != NULL) && (!IsEmptyStr(references))) {
4207 if (msg->cm_fields[eWeferences] != NULL)
4208 free(msg->cm_fields[eWeferences]);
4209 msg->cm_fields[eWeferences] = strdup(references);
4212 if (preformatted_text != NULL) {
4213 msg->cm_fields[eMesageText] = preformatted_text;
4216 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4223 * Check to see whether we have permission to post a message in the current
4224 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4225 * returns 0 on success.
4227 int CtdlDoIHavePermissionToPostInThisRoom(
4230 const char* RemoteIdentifier,
4236 if (!(CC->logged_in) &&
4237 (PostPublic == POST_LOGGED_IN)) {
4238 snprintf(errmsgbuf, n, "Not logged in.");
4239 return (ERROR + NOT_LOGGED_IN);
4241 else if (PostPublic == CHECK_EXISTANCE) {
4242 return (0); // We're Evaling whether a recipient exists
4244 else if (!(CC->logged_in)) {
4246 if ((CC->room.QRflags & QR_READONLY)) {
4247 snprintf(errmsgbuf, n, "Not logged in.");
4248 return (ERROR + NOT_LOGGED_IN);
4250 if (CC->room.QRflags2 & QR2_MODERATED) {
4251 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4252 return (ERROR + NOT_LOGGED_IN);
4254 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4256 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4262 if ((CC->user.axlevel < AxProbU)
4263 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4264 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4265 return (ERROR + HIGHER_ACCESS_REQUIRED);
4268 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4270 if (ra & UA_POSTALLOWED) {
4271 strcpy(errmsgbuf, "OK to post or reply here");
4275 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4277 * To be thorough, we ought to check to see if the message they are
4278 * replying to is actually a valid one in this room, but unless this
4279 * actually becomes a problem we'll go with high performance instead.
4281 strcpy(errmsgbuf, "OK to reply here");
4285 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4286 /* Clarify what happened with a better error message */
4287 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4288 return (ERROR + HIGHER_ACCESS_REQUIRED);
4291 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4292 return (ERROR + HIGHER_ACCESS_REQUIRED);
4298 * Check to see if the specified user has Internet mail permission
4299 * (returns nonzero if permission is granted)
4301 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4303 /* Do not allow twits to send Internet mail */
4304 if (who->axlevel <= AxProbU) return(0);
4306 /* Globally enabled? */
4307 if (config.c_restrict == 0) return(1);
4309 /* User flagged ok? */
4310 if (who->flags & US_INTERNET) return(2);
4312 /* Admin level access? */
4313 if (who->axlevel >= AxAideU) return(3);
4315 /* No mail for you! */
4321 * Validate recipients, count delivery types and errors, and handle aliasing
4322 * FIXME check for dupes!!!!!
4324 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4325 * were specified, or the number of addresses found invalid.
4327 * Caller needs to free the result using free_recipients()
4329 struct recptypes *validate_recipients(const char *supplied_recipients,
4330 const char *RemoteIdentifier,
4332 struct CitContext *CCC = CC;
4333 struct recptypes *ret;
4334 char *recipients = NULL;
4336 char this_recp[256];
4337 char this_recp_cooked[256];
4344 struct ctdluser tempUS;
4345 struct ctdlroom tempQR;
4346 struct ctdlroom tempQR2;
4352 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4353 if (ret == NULL) return(NULL);
4355 /* Set all strings to null and numeric values to zero */
4356 memset(ret, 0, sizeof(struct recptypes));
4358 if (supplied_recipients == NULL) {
4359 recipients = strdup("");
4362 recipients = strdup(supplied_recipients);
4365 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4366 * actually need, but it's healthier for the heap than doing lots of tiny
4367 * realloc() calls instead.
4369 len = strlen(recipients) + 1024;
4370 ret->errormsg = malloc(len);
4371 ret->recp_local = malloc(len);
4372 ret->recp_internet = malloc(len);
4373 ret->recp_ignet = malloc(len);
4374 ret->recp_room = malloc(len);
4375 ret->display_recp = malloc(len);
4376 ret->recp_orgroom = malloc(len);
4377 org_recp = malloc(len);
4379 ret->errormsg[0] = 0;
4380 ret->recp_local[0] = 0;
4381 ret->recp_internet[0] = 0;
4382 ret->recp_ignet[0] = 0;
4383 ret->recp_room[0] = 0;
4384 ret->recp_orgroom[0] = 0;
4385 ret->display_recp[0] = 0;
4387 ret->recptypes_magic = RECPTYPES_MAGIC;
4389 /* Change all valid separator characters to commas */
4390 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4391 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4392 recipients[i] = ',';
4396 /* Now start extracting recipients... */
4398 while (!IsEmptyStr(recipients)) {
4399 for (i=0; i<=strlen(recipients); ++i) {
4400 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4401 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4402 safestrncpy(this_recp, recipients, i+1);
4404 if (recipients[i] == ',') {
4405 strcpy(recipients, &recipients[i+1]);
4408 strcpy(recipients, "");
4415 if (IsEmptyStr(this_recp))
4417 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4420 strcpy(org_recp, this_recp);
4423 mailtype = alias(this_recp);
4425 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4426 if (this_recp[j]=='_') {
4427 this_recp_cooked[j] = ' ';
4430 this_recp_cooked[j] = this_recp[j];
4433 this_recp_cooked[j] = '\0';
4438 if (!strcasecmp(this_recp, "sysop")) {
4440 strcpy(this_recp, config.c_aideroom);
4441 if (!IsEmptyStr(ret->recp_room)) {
4442 strcat(ret->recp_room, "|");
4444 strcat(ret->recp_room, this_recp);
4446 else if ( (!strncasecmp(this_recp, "room_", 5))
4447 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4449 /* Save room so we can restore it later */
4450 tempQR2 = CCC->room;
4453 /* Check permissions to send mail to this room */
4454 err = CtdlDoIHavePermissionToPostInThisRoom(
4459 0 /* 0 = not a reply */
4468 if (!IsEmptyStr(ret->recp_room)) {
4469 strcat(ret->recp_room, "|");
4471 strcat(ret->recp_room, &this_recp_cooked[5]);
4473 if (!IsEmptyStr(ret->recp_orgroom)) {
4474 strcat(ret->recp_orgroom, "|");
4476 strcat(ret->recp_orgroom, org_recp);
4480 /* Restore room in case something needs it */
4481 CCC->room = tempQR2;
4484 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4486 strcpy(this_recp, tempUS.fullname);
4487 if (!IsEmptyStr(ret->recp_local)) {
4488 strcat(ret->recp_local, "|");
4490 strcat(ret->recp_local, this_recp);
4492 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4494 strcpy(this_recp, tempUS.fullname);
4495 if (!IsEmptyStr(ret->recp_local)) {
4496 strcat(ret->recp_local, "|");
4498 strcat(ret->recp_local, this_recp);
4506 /* Yes, you're reading this correctly: if the target
4507 * domain points back to the local system or an attached
4508 * Citadel directory, the address is invalid. That's
4509 * because if the address were valid, we would have
4510 * already translated it to a local address by now.
4512 if (IsDirectory(this_recp, 0)) {
4517 ++ret->num_internet;
4518 if (!IsEmptyStr(ret->recp_internet)) {
4519 strcat(ret->recp_internet, "|");
4521 strcat(ret->recp_internet, this_recp);
4526 if (!IsEmptyStr(ret->recp_ignet)) {
4527 strcat(ret->recp_ignet, "|");
4529 strcat(ret->recp_ignet, this_recp);
4537 if (IsEmptyStr(errmsg)) {
4538 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4541 snprintf(append, sizeof append, "%s", errmsg);
4543 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4544 if (!IsEmptyStr(ret->errormsg)) {
4545 strcat(ret->errormsg, "; ");
4547 strcat(ret->errormsg, append);
4551 if (IsEmptyStr(ret->display_recp)) {
4552 strcpy(append, this_recp);
4555 snprintf(append, sizeof append, ", %s", this_recp);
4557 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4558 strcat(ret->display_recp, append);
4564 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4565 ret->num_room + ret->num_error) == 0) {
4566 ret->num_error = (-1);
4567 strcpy(ret->errormsg, "No recipients specified.");
4570 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4571 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4572 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4573 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4574 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4575 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4583 * Destructor for struct recptypes
4585 void free_recipients(struct recptypes *valid) {
4587 if (valid == NULL) {
4591 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4592 struct CitContext *CCC = CC;
4593 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4597 if (valid->errormsg != NULL) free(valid->errormsg);
4598 if (valid->recp_local != NULL) free(valid->recp_local);
4599 if (valid->recp_internet != NULL) free(valid->recp_internet);
4600 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4601 if (valid->recp_room != NULL) free(valid->recp_room);
4602 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4603 if (valid->display_recp != NULL) free(valid->display_recp);
4604 if (valid->bounce_to != NULL) free(valid->bounce_to);
4605 if (valid->envelope_from != NULL) free(valid->envelope_from);
4606 if (valid->sending_room != NULL) free(valid->sending_room);
4613 * message entry - mode 0 (normal)
4615 void cmd_ent0(char *entargs)
4617 struct CitContext *CCC = CC;
4622 char supplied_euid[128];
4624 int format_type = 0;
4625 char newusername[256];
4626 char newuseremail[256];
4627 struct CtdlMessage *msg;
4631 struct recptypes *valid = NULL;
4632 struct recptypes *valid_to = NULL;
4633 struct recptypes *valid_cc = NULL;
4634 struct recptypes *valid_bcc = NULL;
4636 int subject_required = 0;
4641 int newuseremail_ok = 0;
4642 char references[SIZ];
4647 post = extract_int(entargs, 0);
4648 extract_token(recp, entargs, 1, '|', sizeof recp);
4649 anon_flag = extract_int(entargs, 2);
4650 format_type = extract_int(entargs, 3);
4651 extract_token(subject, entargs, 4, '|', sizeof subject);
4652 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4653 do_confirm = extract_int(entargs, 6);
4654 extract_token(cc, entargs, 7, '|', sizeof cc);
4655 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4656 switch(CC->room.QRdefaultview) {
4659 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4662 supplied_euid[0] = 0;
4665 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4666 extract_token(references, entargs, 11, '|', sizeof references);
4667 for (ptr=references; *ptr != 0; ++ptr) {
4668 if (*ptr == '!') *ptr = '|';
4671 /* first check to make sure the request is valid. */
4673 err = CtdlDoIHavePermissionToPostInThisRoom(
4678 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4682 cprintf("%d %s\n", err, errmsg);
4686 /* Check some other permission type things. */
4688 if (IsEmptyStr(newusername)) {
4689 strcpy(newusername, CCC->user.fullname);
4691 if ( (CCC->user.axlevel < AxAideU)
4692 && (strcasecmp(newusername, CCC->user.fullname))
4693 && (strcasecmp(newusername, CCC->cs_inet_fn))
4695 cprintf("%d You don't have permission to author messages as '%s'.\n",
4696 ERROR + HIGHER_ACCESS_REQUIRED,
4703 if (IsEmptyStr(newuseremail)) {
4704 newuseremail_ok = 1;
4707 if (!IsEmptyStr(newuseremail)) {
4708 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4709 newuseremail_ok = 1;
4711 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4712 j = num_tokens(CCC->cs_inet_other_emails, '|');
4713 for (i=0; i<j; ++i) {
4714 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4715 if (!strcasecmp(newuseremail, buf)) {
4716 newuseremail_ok = 1;
4722 if (!newuseremail_ok) {
4723 cprintf("%d You don't have permission to author messages as '%s'.\n",
4724 ERROR + HIGHER_ACCESS_REQUIRED,
4730 CCC->cs_flags |= CS_POSTING;
4732 /* In mailbox rooms we have to behave a little differently --
4733 * make sure the user has specified at least one recipient. Then
4734 * validate the recipient(s). We do this for the Mail> room, as
4735 * well as any room which has the "Mailbox" view set - unless it
4736 * is the DRAFTS room which does not require recipients
4739 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4740 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4741 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4742 if (CCC->user.axlevel < AxProbU) {
4743 strcpy(recp, "sysop");
4748 valid_to = validate_recipients(recp, NULL, 0);
4749 if (valid_to->num_error > 0) {
4750 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4751 free_recipients(valid_to);
4755 valid_cc = validate_recipients(cc, NULL, 0);
4756 if (valid_cc->num_error > 0) {
4757 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4758 free_recipients(valid_to);
4759 free_recipients(valid_cc);
4763 valid_bcc = validate_recipients(bcc, NULL, 0);
4764 if (valid_bcc->num_error > 0) {
4765 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4766 free_recipients(valid_to);
4767 free_recipients(valid_cc);
4768 free_recipients(valid_bcc);
4772 /* Recipient required, but none were specified */
4773 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4774 free_recipients(valid_to);
4775 free_recipients(valid_cc);
4776 free_recipients(valid_bcc);
4777 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4781 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4782 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4783 cprintf("%d You do not have permission "
4784 "to send Internet mail.\n",
4785 ERROR + HIGHER_ACCESS_REQUIRED);
4786 free_recipients(valid_to);
4787 free_recipients(valid_cc);
4788 free_recipients(valid_bcc);
4793 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)
4794 && (CCC->user.axlevel < AxNetU) ) {
4795 cprintf("%d Higher access required for network mail.\n",
4796 ERROR + HIGHER_ACCESS_REQUIRED);
4797 free_recipients(valid_to);
4798 free_recipients(valid_cc);
4799 free_recipients(valid_bcc);
4803 if ((RESTRICT_INTERNET == 1)
4804 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4805 && ((CCC->user.flags & US_INTERNET) == 0)
4806 && (!CCC->internal_pgm)) {
4807 cprintf("%d You don't have access to Internet mail.\n",
4808 ERROR + HIGHER_ACCESS_REQUIRED);
4809 free_recipients(valid_to);
4810 free_recipients(valid_cc);
4811 free_recipients(valid_bcc);
4817 /* Is this a room which has anonymous-only or anonymous-option? */
4818 anonymous = MES_NORMAL;
4819 if (CCC->room.QRflags & QR_ANONONLY) {
4820 anonymous = MES_ANONONLY;
4822 if (CCC->room.QRflags & QR_ANONOPT) {
4823 if (anon_flag == 1) { /* only if the user requested it */
4824 anonymous = MES_ANONOPT;
4828 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4832 /* Recommend to the client that the use of a message subject is
4833 * strongly recommended in this room, if either the SUBJECTREQ flag
4834 * is set, or if there is one or more Internet email recipients.
4836 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4837 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4838 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4839 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4841 /* If we're only checking the validity of the request, return
4842 * success without creating the message.
4845 cprintf("%d %s|%d\n", CIT_OK,
4846 ((valid_to != NULL) ? valid_to->display_recp : ""),
4848 free_recipients(valid_to);
4849 free_recipients(valid_cc);
4850 free_recipients(valid_bcc);
4854 /* We don't need these anymore because we'll do it differently below */
4855 free_recipients(valid_to);
4856 free_recipients(valid_cc);
4857 free_recipients(valid_bcc);
4859 /* Read in the message from the client. */
4861 cprintf("%d send message\n", START_CHAT_MODE);
4863 cprintf("%d send message\n", SEND_LISTING);
4866 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4867 CCC->room.QRname, anonymous, format_type,
4868 newusername, newuseremail, subject,
4869 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4872 /* Put together one big recipients struct containing to/cc/bcc all in
4873 * one. This is for the envelope.
4875 char *all_recps = malloc(SIZ * 3);
4876 strcpy(all_recps, recp);
4877 if (!IsEmptyStr(cc)) {
4878 if (!IsEmptyStr(all_recps)) {
4879 strcat(all_recps, ",");
4881 strcat(all_recps, cc);
4883 if (!IsEmptyStr(bcc)) {
4884 if (!IsEmptyStr(all_recps)) {
4885 strcat(all_recps, ",");
4887 strcat(all_recps, bcc);
4889 if (!IsEmptyStr(all_recps)) {
4890 valid = validate_recipients(all_recps, NULL, 0);
4897 if ((valid != NULL) && (valid->num_room == 1))
4899 /* posting into an ML room? set the envelope from
4900 * to the actual mail address so others get a valid
4903 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4907 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4909 cprintf("%ld\n", msgnum);
4911 if (StrLength(CCC->StatusMessage) > 0) {
4912 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4914 else if (msgnum >= 0L) {
4915 client_write(HKEY("Message accepted.\n"));
4918 client_write(HKEY("Internal error.\n"));
4921 if (!CM_IsEmpty(msg, eExclusiveID)) {
4922 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4929 CtdlFreeMessage(msg);
4931 if (valid != NULL) {
4932 free_recipients(valid);
4940 * API function to delete messages which match a set of criteria
4941 * (returns the actual number of messages deleted)
4943 int CtdlDeleteMessages(char *room_name, /* which room */
4944 long *dmsgnums, /* array of msg numbers to be deleted */
4945 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4946 char *content_type /* or "" for any. regular expressions expected. */
4949 struct CitContext *CCC = CC;
4950 struct ctdlroom qrbuf;
4951 struct cdbdata *cdbfr;
4952 long *msglist = NULL;
4953 long *dellist = NULL;
4956 int num_deleted = 0;
4958 struct MetaData smi;
4961 int need_to_free_re = 0;
4963 if (content_type) if (!IsEmptyStr(content_type)) {
4964 regcomp(&re, content_type, 0);
4965 need_to_free_re = 1;
4967 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4968 room_name, num_dmsgnums, content_type);
4970 /* get room record, obtaining a lock... */
4971 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4972 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4974 if (need_to_free_re) regfree(&re);
4975 return (0); /* room not found */
4977 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4979 if (cdbfr != NULL) {
4980 dellist = malloc(cdbfr->len);
4981 msglist = (long *) cdbfr->ptr;
4982 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4983 num_msgs = cdbfr->len / sizeof(long);
4987 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4988 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4989 int have_more_del = 1;
4991 num_msgs = sort_msglist(msglist, num_msgs);
4992 if (num_dmsgnums > 1)
4993 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4996 StrBuf *dbg = NewStrBuf();
4997 for (i = 0; i < num_dmsgnums; i++)
4998 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4999 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
5004 while ((i < num_msgs) && (have_more_del)) {
5007 /* Set/clear a bit for each criterion */
5009 /* 0 messages in the list or a null list means that we are
5010 * interested in deleting any messages which meet the other criteria.
5013 delete_this |= 0x01;
5016 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
5021 if (msglist[i] == dmsgnums[j]) {
5022 delete_this |= 0x01;
5025 have_more_del = (j < num_dmsgnums);
5028 if (have_contenttype) {
5029 GetMetaData(&smi, msglist[i]);
5030 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
5031 delete_this |= 0x02;
5034 delete_this |= 0x02;
5037 /* Delete message only if all bits are set */
5038 if (delete_this == 0x03) {
5039 dellist[num_deleted++] = msglist[i];
5046 StrBuf *dbg = NewStrBuf();
5047 for (i = 0; i < num_deleted; i++)
5048 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
5049 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
5053 num_msgs = sort_msglist(msglist, num_msgs);
5054 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
5055 msglist, (int)(num_msgs * sizeof(long)));
5058 qrbuf.QRhighest = msglist[num_msgs - 1];
5060 qrbuf.QRhighest = 0;
5062 CtdlPutRoomLock(&qrbuf);
5064 /* Go through the messages we pulled out of the index, and decrement
5065 * their reference counts by 1. If this is the only room the message
5066 * was in, the reference count will reach zero and the message will
5067 * automatically be deleted from the database. We do this in a
5068 * separate pass because there might be plug-in hooks getting called,
5069 * and we don't want that happening during an S_ROOMS critical
5073 for (i=0; i<num_deleted; ++i) {
5074 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
5076 AdjRefCountList(dellist, num_deleted, -1);
5078 /* Now free the memory we used, and go away. */
5079 if (msglist != NULL) free(msglist);
5080 if (dellist != NULL) free(dellist);
5081 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
5082 if (need_to_free_re) regfree(&re);
5083 return (num_deleted);
5089 * Check whether the current user has permission to delete messages from
5090 * the current room (returns 1 for yes, 0 for no)
5092 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
5094 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
5095 if (ra & UA_DELETEALLOWED) return(1);
5103 * Delete message from current room
5105 void cmd_dele(char *args)
5114 extract_token(msgset, args, 0, '|', sizeof msgset);
5115 num_msgs = num_tokens(msgset, ',');
5117 cprintf("%d Nothing to do.\n", CIT_OK);
5121 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
5122 cprintf("%d Higher access required.\n",
5123 ERROR + HIGHER_ACCESS_REQUIRED);
5128 * Build our message set to be moved/copied
5130 msgs = malloc(num_msgs * sizeof(long));
5131 for (i=0; i<num_msgs; ++i) {
5132 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5133 msgs[i] = atol(msgtok);
5136 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5140 cprintf("%d %d message%s deleted.\n", CIT_OK,
5141 num_deleted, ((num_deleted != 1) ? "s" : ""));
5143 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
5151 * move or copy a message to another room
5153 void cmd_move(char *args)
5160 char targ[ROOMNAMELEN];
5161 struct ctdlroom qtemp;
5168 extract_token(msgset, args, 0, '|', sizeof msgset);
5169 num_msgs = num_tokens(msgset, ',');
5171 cprintf("%d Nothing to do.\n", CIT_OK);
5175 extract_token(targ, args, 1, '|', sizeof targ);
5176 convert_room_name_macros(targ, sizeof targ);
5177 targ[ROOMNAMELEN - 1] = 0;
5178 is_copy = extract_int(args, 2);
5180 if (CtdlGetRoom(&qtemp, targ) != 0) {
5181 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5185 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5186 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5190 CtdlGetUser(&CC->user, CC->curr_user);
5191 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5193 /* Check for permission to perform this operation.
5194 * Remember: "CC->room" is source, "qtemp" is target.
5198 /* Admins can move/copy */
5199 if (CC->user.axlevel >= AxAideU) permit = 1;
5201 /* Room aides can move/copy */
5202 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5204 /* Permit move/copy from personal rooms */
5205 if ((CC->room.QRflags & QR_MAILBOX)
5206 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5208 /* Permit only copy from public to personal room */
5210 && (!(CC->room.QRflags & QR_MAILBOX))
5211 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5213 /* Permit message removal from collaborative delete rooms */
5214 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5216 /* Users allowed to post into the target room may move into it too. */
5217 if ((CC->room.QRflags & QR_MAILBOX) &&
5218 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5220 /* User must have access to target room */
5221 if (!(ra & UA_KNOWN)) permit = 0;
5224 cprintf("%d Higher access required.\n",
5225 ERROR + HIGHER_ACCESS_REQUIRED);
5230 * Build our message set to be moved/copied
5232 msgs = malloc(num_msgs * sizeof(long));
5233 for (i=0; i<num_msgs; ++i) {
5234 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5235 msgs[i] = atol(msgtok);
5241 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5243 cprintf("%d Cannot store message(s) in %s: error %d\n",
5249 /* Now delete the message from the source room,
5250 * if this is a 'move' rather than a 'copy' operation.
5253 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5257 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5263 * GetMetaData() - Get the supplementary record for a message
5265 void GetMetaData(struct MetaData *smibuf, long msgnum)
5268 struct cdbdata *cdbsmi;
5271 memset(smibuf, 0, sizeof(struct MetaData));
5272 smibuf->meta_msgnum = msgnum;
5273 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5275 /* Use the negative of the message number for its supp record index */
5276 TheIndex = (0L - msgnum);
5278 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5279 if (cdbsmi == NULL) {
5280 return; /* record not found; go with defaults */
5282 memcpy(smibuf, cdbsmi->ptr,
5283 ((cdbsmi->len > sizeof(struct MetaData)) ?
5284 sizeof(struct MetaData) : cdbsmi->len));
5291 * PutMetaData() - (re)write supplementary record for a message
5293 void PutMetaData(struct MetaData *smibuf)
5297 /* Use the negative of the message number for the metadata db index */
5298 TheIndex = (0L - smibuf->meta_msgnum);
5300 cdb_store(CDB_MSGMAIN,
5301 &TheIndex, (int)sizeof(long),
5302 smibuf, (int)sizeof(struct MetaData));
5307 * AdjRefCount - submit an adjustment to the reference count for a message.
5308 * (These are just queued -- we actually process them later.)
5310 void AdjRefCount(long msgnum, int incr)
5312 struct CitContext *CCC = CC;
5313 struct arcq new_arcq;
5316 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5318 begin_critical_section(S_SUPPMSGMAIN);
5319 if (arcfp == NULL) {
5320 arcfp = fopen(file_arcq, "ab+");
5321 chown(file_arcq, CTDLUID, (-1));
5322 chmod(file_arcq, 0600);
5324 end_critical_section(S_SUPPMSGMAIN);
5326 /* msgnum < 0 means that we're trying to close the file */
5328 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5329 begin_critical_section(S_SUPPMSGMAIN);
5330 if (arcfp != NULL) {
5334 end_critical_section(S_SUPPMSGMAIN);
5339 * If we can't open the queue, perform the operation synchronously.
5341 if (arcfp == NULL) {
5342 TDAP_AdjRefCount(msgnum, incr);
5346 new_arcq.arcq_msgnum = msgnum;
5347 new_arcq.arcq_delta = incr;
5348 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5350 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5359 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5361 struct CitContext *CCC = CC;
5362 long i, the_size, offset;
5363 struct arcq *new_arcq;
5366 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5368 begin_critical_section(S_SUPPMSGMAIN);
5369 if (arcfp == NULL) {
5370 arcfp = fopen(file_arcq, "ab+");
5371 chown(file_arcq, CTDLUID, (-1));
5372 chmod(file_arcq, 0600);
5374 end_critical_section(S_SUPPMSGMAIN);
5377 * If we can't open the queue, perform the operation synchronously.
5379 if (arcfp == NULL) {
5380 for (i = 0; i < nmsg; i++)
5381 TDAP_AdjRefCount(msgnum[i], incr);
5385 the_size = sizeof(struct arcq) * nmsg;
5386 new_arcq = malloc(the_size);
5387 for (i = 0; i < nmsg; i++) {
5388 new_arcq[i].arcq_msgnum = msgnum[i];
5389 new_arcq[i].arcq_delta = incr;
5393 while ((rv >= 0) && (offset < the_size))
5395 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5397 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5413 * TDAP_ProcessAdjRefCountQueue()
5415 * Process the queue of message count adjustments that was created by calls
5416 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5417 * for each one. This should be an "off hours" operation.
5419 int TDAP_ProcessAdjRefCountQueue(void)
5421 struct CitContext *CCC = CC;
5422 char file_arcq_temp[PATH_MAX];
5425 struct arcq arcq_rec;
5426 int num_records_processed = 0;
5428 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5430 begin_critical_section(S_SUPPMSGMAIN);
5431 if (arcfp != NULL) {
5436 r = link(file_arcq, file_arcq_temp);
5438 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5439 end_critical_section(S_SUPPMSGMAIN);
5440 return(num_records_processed);
5444 end_critical_section(S_SUPPMSGMAIN);
5446 fp = fopen(file_arcq_temp, "rb");
5448 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5449 return(num_records_processed);
5452 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5453 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5454 ++num_records_processed;
5458 r = unlink(file_arcq_temp);
5460 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5463 return(num_records_processed);
5469 * TDAP_AdjRefCount - adjust the reference count for a message.
5470 * This one does it "for real" because it's called by
5471 * the autopurger function that processes the queue
5472 * created by AdjRefCount(). If a message's reference
5473 * count becomes zero, we also delete the message from
5474 * disk and de-index it.
5476 void TDAP_AdjRefCount(long msgnum, int incr)
5478 struct CitContext *CCC = CC;
5480 struct MetaData smi;
5483 /* This is a *tight* critical section; please keep it that way, as
5484 * it may get called while nested in other critical sections.
5485 * Complicating this any further will surely cause deadlock!
5487 begin_critical_section(S_SUPPMSGMAIN);
5488 GetMetaData(&smi, msgnum);
5489 smi.meta_refcount += incr;
5491 end_critical_section(S_SUPPMSGMAIN);
5492 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5493 msgnum, incr, smi.meta_refcount
5496 /* If the reference count is now zero, delete the message
5497 * (and its supplementary record as well).
5499 if (smi.meta_refcount == 0) {
5500 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5502 /* Call delete hooks with NULL room to show it has gone altogether */
5503 PerformDeleteHooks(NULL, msgnum);
5505 /* Remove from message base */
5507 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5508 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5510 /* Remove metadata record */
5511 delnum = (0L - msgnum);
5512 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5518 * Write a generic object to this room
5520 * Note: this could be much more efficient. Right now we use two temporary
5521 * files, and still pull the message into memory as with all others.
5523 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5524 char *content_type, /* MIME type of this object */
5525 char *raw_message, /* Data to be written */
5526 off_t raw_length, /* Size of raw_message */
5527 struct ctdluser *is_mailbox, /* Mailbox room? */
5528 int is_binary, /* Is encoding necessary? */
5529 int is_unique, /* Del others of this type? */
5530 unsigned int flags /* Internal save flags */
5533 struct CitContext *CCC = CC;
5534 struct ctdlroom qrbuf;
5535 char roomname[ROOMNAMELEN];
5536 struct CtdlMessage *msg;
5537 char *encoded_message = NULL;
5539 if (is_mailbox != NULL) {
5540 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5543 safestrncpy(roomname, req_room, sizeof(roomname));
5546 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5549 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5552 encoded_message = malloc((size_t)(raw_length + 4096));
5555 sprintf(encoded_message, "Content-type: %s\n", content_type);
5558 sprintf(&encoded_message[strlen(encoded_message)],
5559 "Content-transfer-encoding: base64\n\n"
5563 sprintf(&encoded_message[strlen(encoded_message)],
5564 "Content-transfer-encoding: 7bit\n\n"
5570 &encoded_message[strlen(encoded_message)],
5578 &encoded_message[strlen(encoded_message)],
5584 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5585 msg = malloc(sizeof(struct CtdlMessage));
5586 memset(msg, 0, sizeof(struct CtdlMessage));
5587 msg->cm_magic = CTDLMESSAGE_MAGIC;
5588 msg->cm_anon_type = MES_NORMAL;
5589 msg->cm_format_type = 4;
5590 msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
5591 msg->cm_fields[eOriginalRoom] = strdup(req_room);
5592 msg->cm_fields[eNodeName] = strdup(config.c_nodename);
5593 msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
5594 msg->cm_flags = flags;
5596 msg->cm_fields[eMesageText] = encoded_message;
5598 /* Create the requested room if we have to. */
5599 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5600 CtdlCreateRoom(roomname,
5601 ( (is_mailbox != NULL) ? 5 : 3 ),
5602 "", 0, 1, 0, VIEW_BBS);
5604 /* If the caller specified this object as unique, delete all
5605 * other objects of this type that are currently in the room.
5608 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5609 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5612 /* Now write the data */
5613 CtdlSubmitMsg(msg, NULL, roomname, 0);
5614 CtdlFreeMessage(msg);
5622 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5623 config_msgnum = msgnum;
5627 char *CtdlGetSysConfig(char *sysconfname) {
5628 char hold_rm[ROOMNAMELEN];
5631 struct CtdlMessage *msg;
5634 strcpy(hold_rm, CC->room.QRname);
5635 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5636 CtdlGetRoom(&CC->room, hold_rm);
5641 /* We want the last (and probably only) config in this room */
5642 begin_critical_section(S_CONFIG);
5643 config_msgnum = (-1L);
5644 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5645 CtdlGetSysConfigBackend, NULL);
5646 msgnum = config_msgnum;
5647 end_critical_section(S_CONFIG);
5653 msg = CtdlFetchMessage(msgnum, 1);
5655 conf = strdup(msg->cm_fields[eMesageText]);
5656 CtdlFreeMessage(msg);
5663 CtdlGetRoom(&CC->room, hold_rm);
5665 if (conf != NULL) do {
5666 extract_token(buf, conf, 0, '\n', sizeof buf);
5667 strcpy(conf, &conf[strlen(buf)+1]);
5668 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5674 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5675 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5680 * Determine whether a given Internet address belongs to the current user
5682 int CtdlIsMe(char *addr, int addr_buf_len)
5684 struct recptypes *recp;
5687 recp = validate_recipients(addr, NULL, 0);
5688 if (recp == NULL) return(0);
5690 if (recp->num_local == 0) {
5691 free_recipients(recp);
5695 for (i=0; i<recp->num_local; ++i) {
5696 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5697 if (!strcasecmp(addr, CC->user.fullname)) {
5698 free_recipients(recp);
5703 free_recipients(recp);
5709 * Citadel protocol command to do the same
5711 void cmd_isme(char *argbuf) {
5714 if (CtdlAccessCheck(ac_logged_in)) return;
5715 extract_token(addr, argbuf, 0, '|', sizeof addr);
5717 if (CtdlIsMe(addr, sizeof addr)) {
5718 cprintf("%d %s\n", CIT_OK, addr);
5721 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5727 /*****************************************************************************/
5728 /* MODULE INITIALIZATION STUFF */
5729 /*****************************************************************************/
5730 void SetMessageDebugEnabled(const int n)
5732 MessageDebugEnabled = n;
5734 CTDL_MODULE_INIT(msgbase)
5737 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5739 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5740 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5741 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5742 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5743 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5744 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5745 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5746 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5747 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5748 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5749 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5750 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5753 /* return our Subversion id for the Log */