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 * This function is self explanatory.
273 * (What can I say, I'm in a weird mood today...)
275 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
279 for (i = 0; i < strlen(name); ++i) {
280 if (name[i] == '@') {
281 while (isspace(name[i - 1]) && i > 0) {
282 strcpy(&name[i - 1], &name[i]);
285 while (isspace(name[i + 1])) {
286 strcpy(&name[i + 1], &name[i + 2]);
294 * Aliasing for network mail.
295 * (Error messages have been commented out, because this is a server.)
297 int alias(char *name)
298 { /* process alias and routing info for mail */
299 struct CitContext *CCC = CC;
302 char aaa[SIZ], bbb[SIZ];
303 char *ignetcfg = NULL;
304 char *ignetmap = NULL;
310 char original_name[256];
311 safestrncpy(original_name, name, sizeof original_name);
314 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
315 stripallbut(name, '<', '>');
317 fp = fopen(file_mail_aliases, "r");
319 fp = fopen("/dev/null", "r");
326 while (fgets(aaa, sizeof aaa, fp) != NULL) {
327 while (isspace(name[0]))
328 strcpy(name, &name[1]);
329 aaa[strlen(aaa) - 1] = 0;
331 for (a = 0; a < strlen(aaa); ++a) {
333 strcpy(bbb, &aaa[a + 1]);
337 if (!strcasecmp(name, aaa))
342 /* Hit the Global Address Book */
343 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
347 if (strcasecmp(original_name, name)) {
348 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
351 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
352 for (a=0; a<strlen(name); ++a) {
353 if (name[a] == '@') {
354 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
356 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
361 /* determine local or remote type, see citadel.h */
362 at = haschar(name, '@');
363 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
364 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
365 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
367 /* figure out the delivery mode */
368 extract_token(node, name, 1, '@', sizeof node);
370 /* If there are one or more dots in the nodename, we assume that it
371 * is an FQDN and will attempt SMTP delivery to the Internet.
373 if (haschar(node, '.') > 0) {
374 return(MES_INTERNET);
377 /* Otherwise we look in the IGnet maps for a valid Citadel node.
378 * Try directly-connected nodes first...
380 ignetcfg = CtdlGetSysConfig(IGNETCFG);
381 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
382 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
383 extract_token(testnode, buf, 0, '|', sizeof testnode);
384 if (!strcasecmp(node, testnode)) {
392 * Then try nodes that are two or more hops away.
394 ignetmap = CtdlGetSysConfig(IGNETMAP);
395 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
396 extract_token(buf, ignetmap, i, '\n', sizeof buf);
397 extract_token(testnode, buf, 0, '|', sizeof testnode);
398 if (!strcasecmp(node, testnode)) {
405 /* If we get to this point it's an invalid node name */
411 * Back end for the MSGS command: output message number only.
413 void simple_listing(long msgnum, void *userdata)
415 cprintf("%ld\n", msgnum);
421 * Back end for the MSGS command: output header summary.
423 void headers_listing(long msgnum, void *userdata)
425 struct CtdlMessage *msg;
427 msg = CtdlFetchMessage(msgnum, 0);
429 cprintf("%ld|0|||||\n", msgnum);
433 cprintf("%ld|%s|%s|%s|%s|%s|\n",
435 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"),
436 (!CM_IsEmpty(msg, eAuthor) ? msg->cm_fields[eAuthor] : ""),
437 (!CM_IsEmpty(msg, eNodeName) ? msg->cm_fields[eNodeName] : ""),
438 (!CM_IsEmpty(msg, erFc822Addr) ? msg->cm_fields[erFc822Addr] : ""),
439 (!CM_IsEmpty(msg, eMsgSubject) ? msg->cm_fields[eMsgSubject] : "")
441 CtdlFreeMessage(msg);
445 * Back end for the MSGS command: output EUID header.
447 void headers_euid(long msgnum, void *userdata)
449 struct CtdlMessage *msg;
451 msg = CtdlFetchMessage(msgnum, 0);
453 cprintf("%ld||\n", msgnum);
457 cprintf("%ld|%s|%s\n",
459 (!CM_IsEmpty(msg, eExclusiveID) ? msg->cm_fields[eExclusiveID] : ""),
460 (!CM_IsEmpty(msg, eTimestamp) ? msg->cm_fields[eTimestamp] : "0"));
461 CtdlFreeMessage(msg);
468 /* Determine if a given message matches the fields in a message template.
469 * Return 0 for a successful match.
471 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
474 /* If there aren't any fields in the template, all messages will
477 if (template == NULL) return(0);
479 /* Null messages are bogus. */
480 if (msg == NULL) return(1);
482 for (i='A'; i<='Z'; ++i) {
483 if (template->cm_fields[i] != NULL) {
484 if (msg->cm_fields[i] == NULL) {
485 /* Considered equal if temmplate is empty string */
486 if (IsEmptyStr(template->cm_fields[i])) continue;
489 if (strcasecmp(msg->cm_fields[i],
490 template->cm_fields[i])) return 1;
494 /* All compares succeeded: we have a match! */
501 * Retrieve the "seen" message list for the current room.
503 void CtdlGetSeen(char *buf, int which_set) {
504 struct CitContext *CCC = CC;
507 /* Learn about the user and room in question */
508 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
510 if (which_set == ctdlsetseen_seen)
511 safestrncpy(buf, vbuf.v_seen, SIZ);
512 if (which_set == ctdlsetseen_answered)
513 safestrncpy(buf, vbuf.v_answered, SIZ);
519 * Manipulate the "seen msgs" string (or other message set strings)
521 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
522 int target_setting, int which_set,
523 struct ctdluser *which_user, struct ctdlroom *which_room) {
524 struct CitContext *CCC = CC;
525 struct cdbdata *cdbfr;
530 long hi = (-1L); /// TODO: we just write here. y?
539 char *is_set; /* actually an array of booleans */
541 /* Don't bother doing *anything* if we were passed a list of zero messages */
542 if (num_target_msgnums < 1) {
546 /* If no room was specified, we go with the current room. */
548 which_room = &CCC->room;
551 /* If no user was specified, we go with the current user. */
553 which_user = &CCC->user;
556 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
557 num_target_msgnums, target_msgnums[0],
558 (target_setting ? "SET" : "CLEAR"),
562 /* Learn about the user and room in question */
563 CtdlGetRelationship(&vbuf, which_user, which_room);
565 /* Load the message list */
566 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
568 msglist = (long *) cdbfr->ptr;
569 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
570 num_msgs = cdbfr->len / sizeof(long);
573 return; /* No messages at all? No further action. */
576 is_set = malloc(num_msgs * sizeof(char));
577 memset(is_set, 0, (num_msgs * sizeof(char)) );
579 /* Decide which message set we're manipulating */
581 case ctdlsetseen_seen:
582 vset = NewStrBufPlain(vbuf.v_seen, -1);
584 case ctdlsetseen_answered:
585 vset = NewStrBufPlain(vbuf.v_answered, -1);
592 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
593 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
594 for (i=0; i<num_msgs; ++i) {
595 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
597 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
598 for (k=0; k<num_target_msgnums; ++k) {
599 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
603 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
605 /* Translate the existing sequence set into an array of booleans */
606 setstr = NewStrBuf();
610 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
612 StrBufExtract_token(lostr, setstr, 0, ':');
613 if (StrBufNum_tokens(setstr, ':') >= 2) {
614 StrBufExtract_token(histr, setstr, 1, ':');
618 StrBufAppendBuf(histr, lostr, 0);
621 if (!strcmp(ChrPtr(histr), "*")) {
628 for (i = 0; i < num_msgs; ++i) {
629 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
639 /* Now translate the array of booleans back into a sequence set */
645 for (i=0; i<num_msgs; ++i) {
649 for (k=0; k<num_target_msgnums; ++k) {
650 if (msglist[i] == target_msgnums[k]) {
651 is_seen = target_setting;
655 if ((was_seen == 0) && (is_seen == 1)) {
658 else if ((was_seen == 1) && (is_seen == 0)) {
661 if (StrLength(vset) > 0) {
662 StrBufAppendBufPlain(vset, HKEY(","), 0);
665 StrBufAppendPrintf(vset, "%ld", hi);
668 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
672 if ((is_seen) && (i == num_msgs - 1)) {
673 if (StrLength(vset) > 0) {
674 StrBufAppendBufPlain(vset, HKEY(","), 0);
676 if ((i==0) || (was_seen == 0)) {
677 StrBufAppendPrintf(vset, "%ld", msglist[i]);
680 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
688 * We will have to stuff this string back into a 4096 byte buffer, so if it's
689 * larger than that now, truncate it by removing tokens from the beginning.
690 * The limit of 100 iterations is there to prevent an infinite loop in case
691 * something unexpected happens.
693 int number_of_truncations = 0;
694 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
695 StrBufRemove_token(vset, 0, ',');
696 ++number_of_truncations;
700 * If we're truncating the sequence set of messages marked with the 'seen' flag,
701 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
702 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
704 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
706 first_tok = NewStrBuf();
707 StrBufExtract_token(first_tok, vset, 0, ',');
708 StrBufRemove_token(vset, 0, ',');
710 if (StrBufNum_tokens(first_tok, ':') > 1) {
711 StrBufRemove_token(first_tok, 0, ':');
715 new_set = NewStrBuf();
716 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
717 StrBufAppendBuf(new_set, first_tok, 0);
718 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
719 StrBufAppendBuf(new_set, vset, 0);
722 FreeStrBuf(&first_tok);
726 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
728 /* Decide which message set we're manipulating */
730 case ctdlsetseen_seen:
731 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
733 case ctdlsetseen_answered:
734 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
740 CtdlSetRelationship(&vbuf, which_user, which_room);
746 * API function to perform an operation for each qualifying message in the
747 * current room. (Returns the number of messages processed.)
749 int CtdlForEachMessage(int mode, long ref, char *search_string,
751 struct CtdlMessage *compare,
752 ForEachMsgCallback CallBack,
755 struct CitContext *CCC = CC;
758 struct cdbdata *cdbfr;
759 long *msglist = NULL;
761 int num_processed = 0;
764 struct CtdlMessage *msg = NULL;
767 int printed_lastold = 0;
768 int num_search_msgs = 0;
769 long *search_msgs = NULL;
771 int need_to_free_re = 0;
774 if ((content_type) && (!IsEmptyStr(content_type))) {
775 regcomp(&re, content_type, 0);
779 /* Learn about the user and room in question */
780 if (server_shutting_down) {
781 if (need_to_free_re) regfree(&re);
784 CtdlGetUser(&CCC->user, CCC->curr_user);
786 if (server_shutting_down) {
787 if (need_to_free_re) regfree(&re);
790 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
792 if (server_shutting_down) {
793 if (need_to_free_re) regfree(&re);
797 /* Load the message list */
798 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
800 if (need_to_free_re) regfree(&re);
801 return 0; /* No messages at all? No further action. */
804 msglist = (long *) cdbfr->ptr;
805 num_msgs = cdbfr->len / sizeof(long);
807 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
808 cdb_free(cdbfr); /* we own this memory now */
811 * Now begin the traversal.
813 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
815 /* If the caller is looking for a specific MIME type, filter
816 * out all messages which are not of the type requested.
818 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
820 /* This call to GetMetaData() sits inside this loop
821 * so that we only do the extra database read per msg
822 * if we need to. Doing the extra read all the time
823 * really kills the server. If we ever need to use
824 * metadata for another search criterion, we need to
825 * move the read somewhere else -- but still be smart
826 * enough to only do the read if the caller has
827 * specified something that will need it.
829 if (server_shutting_down) {
830 if (need_to_free_re) regfree(&re);
834 GetMetaData(&smi, msglist[a]);
836 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
837 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
843 num_msgs = sort_msglist(msglist, num_msgs);
845 /* If a template was supplied, filter out the messages which
846 * don't match. (This could induce some delays!)
849 if (compare != NULL) {
850 for (a = 0; a < num_msgs; ++a) {
851 if (server_shutting_down) {
852 if (need_to_free_re) regfree(&re);
856 msg = CtdlFetchMessage(msglist[a], 1);
858 if (CtdlMsgCmp(msg, compare)) {
861 CtdlFreeMessage(msg);
867 /* If a search string was specified, get a message list from
868 * the full text index and remove messages which aren't on both
872 * Since the lists are sorted and strictly ascending, and the
873 * output list is guaranteed to be shorter than or equal to the
874 * input list, we overwrite the bottom of the input list. This
875 * eliminates the need to memmove big chunks of the list over and
878 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
880 /* Call search module via hook mechanism.
881 * NULL means use any search function available.
882 * otherwise replace with a char * to name of search routine
884 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
886 if (num_search_msgs > 0) {
890 orig_num_msgs = num_msgs;
892 for (i=0; i<orig_num_msgs; ++i) {
893 for (j=0; j<num_search_msgs; ++j) {
894 if (msglist[i] == search_msgs[j]) {
895 msglist[num_msgs++] = msglist[i];
901 num_msgs = 0; /* No messages qualify */
903 if (search_msgs != NULL) free(search_msgs);
905 /* Now that we've purged messages which don't contain the search
906 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
913 * Now iterate through the message list, according to the
914 * criteria supplied by the caller.
917 for (a = 0; a < num_msgs; ++a) {
918 if (server_shutting_down) {
919 if (need_to_free_re) regfree(&re);
921 return num_processed;
923 thismsg = msglist[a];
924 if (mode == MSGS_ALL) {
928 is_seen = is_msg_in_sequence_set(
929 vbuf.v_seen, thismsg);
930 if (is_seen) lastold = thismsg;
936 || ((mode == MSGS_OLD) && (is_seen))
937 || ((mode == MSGS_NEW) && (!is_seen))
938 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
939 || ((mode == MSGS_FIRST) && (a < ref))
940 || ((mode == MSGS_GT) && (thismsg > ref))
941 || ((mode == MSGS_LT) && (thismsg < ref))
942 || ((mode == MSGS_EQ) && (thismsg == ref))
945 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
947 CallBack(lastold, userdata);
951 if (CallBack) CallBack(thismsg, userdata);
955 if (need_to_free_re) regfree(&re);
958 * We cache the most recent msglist in order to do security checks later
960 if (CCC->client_socket > 0) {
961 if (CCC->cached_msglist != NULL) {
962 free(CCC->cached_msglist);
964 CCC->cached_msglist = msglist;
965 CCC->cached_num_msgs = num_msgs;
971 return num_processed;
977 * cmd_msgs() - get list of message #'s in this room
978 * implements the MSGS server command using CtdlForEachMessage()
980 void cmd_msgs(char *cmdbuf)
989 int with_template = 0;
990 struct CtdlMessage *template = NULL;
991 char search_string[1024];
992 ForEachMsgCallback CallBack;
994 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
996 extract_token(which, cmdbuf, 0, '|', sizeof which);
997 cm_ref = extract_int(cmdbuf, 1);
998 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
999 with_template = extract_int(cmdbuf, 2);
1000 switch (extract_int(cmdbuf, 3))
1003 case MSG_HDRS_BRIEF:
1004 CallBack = simple_listing;
1007 CallBack = headers_listing;
1010 CallBack = headers_euid;
1015 if (!strncasecmp(which, "OLD", 3))
1017 else if (!strncasecmp(which, "NEW", 3))
1019 else if (!strncasecmp(which, "FIRST", 5))
1021 else if (!strncasecmp(which, "LAST", 4))
1023 else if (!strncasecmp(which, "GT", 2))
1025 else if (!strncasecmp(which, "LT", 2))
1027 else if (!strncasecmp(which, "SEARCH", 6))
1032 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
1033 cprintf("%d Full text index is not enabled on this server.\n",
1034 ERROR + CMD_NOT_SUPPORTED);
1038 if (with_template) {
1040 cprintf("%d Send template then receive message list\n",
1042 template = (struct CtdlMessage *)
1043 malloc(sizeof(struct CtdlMessage));
1044 memset(template, 0, sizeof(struct CtdlMessage));
1045 template->cm_magic = CTDLMESSAGE_MAGIC;
1046 template->cm_anon_type = MES_NORMAL;
1048 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
1050 extract_token(tfield, buf, 0, '|', sizeof tfield);
1051 tValueLen = extract_token(tvalue, buf, 1, '|', sizeof tvalue);
1052 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
1053 if (!strcasecmp(tfield, msgkeys[i])) {
1054 CM_SetField(template, i, tvalue, tValueLen);
1061 cprintf("%d \n", LISTING_FOLLOWS);
1064 CtdlForEachMessage(mode,
1065 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
1066 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
1071 if (template != NULL) CtdlFreeMessage(template);
1079 * help_subst() - support routine for help file viewer
1081 void help_subst(char *strbuf, char *source, char *dest)
1086 while (p = pattern2(strbuf, source), (p >= 0)) {
1087 strcpy(workbuf, &strbuf[p + strlen(source)]);
1088 strcpy(&strbuf[p], dest);
1089 strcat(strbuf, workbuf);
1094 void do_help_subst(char *buffer)
1098 help_subst(buffer, "^nodename", config.c_nodename);
1099 help_subst(buffer, "^humannode", config.c_humannode);
1100 help_subst(buffer, "^fqdn", config.c_fqdn);
1101 help_subst(buffer, "^username", CC->user.fullname);
1102 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
1103 help_subst(buffer, "^usernum", buf2);
1104 help_subst(buffer, "^sysadm", config.c_sysadm);
1105 help_subst(buffer, "^variantname", CITADEL);
1106 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
1107 help_subst(buffer, "^maxsessions", buf2);
1108 help_subst(buffer, "^bbsdir", ctdl_message_dir);
1114 * memfmout() - Citadel text formatter and paginator.
1115 * Although the original purpose of this routine was to format
1116 * text to the reader's screen width, all we're really using it
1117 * for here is to format text out to 80 columns before sending it
1118 * to the client. The client software may reformat it again.
1121 char *mptr, /* where are we going to get our text from? */
1122 const char *nl /* string to terminate lines with */
1124 struct CitContext *CCC = CC;
1126 unsigned char ch = 0;
1133 while (ch=*(mptr++), ch != 0) {
1136 if (client_write(outbuf, len) == -1)
1138 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1142 if (client_write(nl, nllen) == -1)
1144 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1149 else if (ch == '\r') {
1150 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1152 else if (isspace(ch)) {
1153 if (column > 72) { /* Beyond 72 columns, break on the next space */
1154 if (client_write(outbuf, len) == -1)
1156 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1160 if (client_write(nl, nllen) == -1)
1162 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1175 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1176 if (client_write(outbuf, len) == -1)
1178 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1182 if (client_write(nl, nllen) == -1)
1184 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1192 if (client_write(outbuf, len) == -1)
1194 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1198 client_write(nl, nllen);
1206 * Callback function for mime parser that simply lists the part
1208 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1209 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1210 char *cbid, void *cbuserdata)
1214 ma = (struct ma_info *)cbuserdata;
1215 if (ma->is_ma == 0) {
1216 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1229 * Callback function for multipart prefix
1231 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1232 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1233 char *cbid, void *cbuserdata)
1237 ma = (struct ma_info *)cbuserdata;
1238 if (!strcasecmp(cbtype, "multipart/alternative")) {
1242 if (ma->is_ma == 0) {
1243 cprintf("pref=%s|%s\n", partnum, cbtype);
1248 * Callback function for multipart sufffix
1250 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1251 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1252 char *cbid, void *cbuserdata)
1256 ma = (struct ma_info *)cbuserdata;
1257 if (ma->is_ma == 0) {
1258 cprintf("suff=%s|%s\n", partnum, cbtype);
1260 if (!strcasecmp(cbtype, "multipart/alternative")) {
1267 * Callback function for mime parser that opens a section for downloading
1269 void mime_download(char *name, char *filename, char *partnum, char *disp,
1270 void *content, char *cbtype, char *cbcharset, size_t length,
1271 char *encoding, char *cbid, void *cbuserdata)
1274 CitContext *CCC = MyContext();
1276 /* Silently go away if there's already a download open. */
1277 if (CCC->download_fp != NULL)
1281 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1282 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1284 CCC->download_fp = tmpfile();
1285 if (CCC->download_fp == NULL) {
1286 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1288 cprintf("%d cannot open temporary file: %s\n",
1289 ERROR + INTERNAL_ERROR, strerror(errno));
1293 rv = fwrite(content, length, 1, CCC->download_fp);
1295 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1297 cprintf("%d unable to write tempfile.\n",
1299 fclose(CCC->download_fp);
1300 CCC->download_fp = NULL;
1303 fflush(CCC->download_fp);
1304 rewind(CCC->download_fp);
1306 OpenCmdResult(filename, cbtype);
1313 * Callback function for mime parser that outputs a section all at once.
1314 * We can specify the desired section by part number *or* content-id.
1316 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1317 void *content, char *cbtype, char *cbcharset, size_t length,
1318 char *encoding, char *cbid, void *cbuserdata)
1320 int *found_it = (int *)cbuserdata;
1323 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1324 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1327 cprintf("%d %d|-1|%s|%s|%s\n",
1334 client_write(content, length);
1340 * Load a message from disk into memory.
1341 * This is used by CtdlOutputMsg() and other fetch functions.
1343 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1344 * using the CtdlMessageFree() function.
1346 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1348 struct CitContext *CCC = CC;
1349 struct cdbdata *dmsgtext;
1350 struct CtdlMessage *ret = NULL;
1354 cit_uint8_t field_header;
1356 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1357 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1358 if (dmsgtext == NULL) {
1359 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1362 mptr = dmsgtext->ptr;
1363 upper_bound = mptr + dmsgtext->len;
1365 /* Parse the three bytes that begin EVERY message on disk.
1366 * The first is always 0xFF, the on-disk magic number.
1367 * The second is the anonymous/public type byte.
1368 * The third is the format type byte (vari, fixed, or MIME).
1372 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1376 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1377 memset(ret, 0, sizeof(struct CtdlMessage));
1379 ret->cm_magic = CTDLMESSAGE_MAGIC;
1380 ret->cm_anon_type = *mptr++; /* Anon type byte */
1381 ret->cm_format_type = *mptr++; /* Format type byte */
1384 * The rest is zero or more arbitrary fields. Load them in.
1385 * We're done when we encounter either a zero-length field or
1386 * have just processed the 'M' (message text) field.
1390 if (mptr >= upper_bound) {
1393 field_header = *mptr++;
1395 CM_SetField(ret, field_header, mptr, len);
1397 mptr += len + 1; /* advance to next field */
1399 } while ((mptr < upper_bound) && (field_header != 'M'));
1403 /* Always make sure there's something in the msg text field. If
1404 * it's NULL, the message text is most likely stored separately,
1405 * so go ahead and fetch that. Failing that, just set a dummy
1406 * body so other code doesn't barf.
1408 if ( (CM_IsEmpty(ret, eMesageText)) && (with_body) ) {
1409 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1410 if (dmsgtext != NULL) {
1411 CM_SetAsField(ret, eMesageText, &dmsgtext->ptr, dmsgtext->len);
1415 if (CM_IsEmpty(ret, eMesageText)) {
1416 CM_SetField(ret, eMesageText, HKEY("\r\n\r\n (no text)\r\n"));
1419 /* Perform "before read" hooks (aborting if any return nonzero) */
1420 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1421 CtdlFreeMessage(ret);
1430 * Returns 1 if the supplied pointer points to a valid Citadel message.
1431 * If the pointer is NULL or the magic number check fails, returns 0.
1433 int is_valid_message(struct CtdlMessage *msg) {
1436 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1437 struct CitContext *CCC = CC;
1438 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1444 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1448 for (i = 0; i < 256; ++i)
1449 if (msg->cm_fields[i] != NULL) {
1450 free(msg->cm_fields[i]);
1453 msg->cm_magic = 0; /* just in case */
1456 * 'Destructor' for struct CtdlMessage
1458 void CtdlFreeMessage(struct CtdlMessage *msg)
1460 if (is_valid_message(msg) == 0)
1462 if (msg != NULL) free (msg);
1465 CtdlFreeMessageContents(msg);
1469 int DupCMField(eMsgField i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
1472 len = strlen(OrgMsg->cm_fields[i]);
1473 NewMsg->cm_fields[i] = malloc(len + 1);
1474 if (NewMsg->cm_fields[i] == NULL)
1476 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
1477 NewMsg->cm_fields[i][len] = '\0';
1481 struct CtdlMessage * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
1484 struct CtdlMessage *NewMsg;
1486 if (is_valid_message(OrgMsg) == 0)
1488 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
1492 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
1494 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
1496 for (i = 0; i < 256; ++i)
1498 if (OrgMsg->cm_fields[i] != NULL)
1500 if (!DupCMField(i, OrgMsg, NewMsg))
1502 CtdlFreeMessage(NewMsg);
1514 * Pre callback function for multipart/alternative
1516 * NOTE: this differs from the standard behavior for a reason. Normally when
1517 * displaying multipart/alternative you want to show the _last_ usable
1518 * format in the message. Here we show the _first_ one, because it's
1519 * usually text/plain. Since this set of functions is designed for text
1520 * output to non-MIME-aware clients, this is the desired behavior.
1523 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1524 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1525 char *cbid, void *cbuserdata)
1527 struct CitContext *CCC = CC;
1530 ma = (struct ma_info *)cbuserdata;
1531 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1532 if (!strcasecmp(cbtype, "multipart/alternative")) {
1536 if (!strcasecmp(cbtype, "message/rfc822")) {
1542 * Post callback function for multipart/alternative
1544 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1545 void *content, char *cbtype, char *cbcharset, size_t length,
1546 char *encoding, char *cbid, void *cbuserdata)
1548 struct CitContext *CCC = CC;
1551 ma = (struct ma_info *)cbuserdata;
1552 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1553 if (!strcasecmp(cbtype, "multipart/alternative")) {
1557 if (!strcasecmp(cbtype, "message/rfc822")) {
1563 * Inline callback function for mime parser that wants to display text
1565 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1566 void *content, char *cbtype, char *cbcharset, size_t length,
1567 char *encoding, char *cbid, void *cbuserdata)
1569 struct CitContext *CCC = CC;
1575 ma = (struct ma_info *)cbuserdata;
1577 MSG_syslog(LOG_DEBUG,
1578 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1579 partnum, filename, cbtype, (long)length);
1582 * If we're in the middle of a multipart/alternative scope and
1583 * we've already printed another section, skip this one.
1585 if ( (ma->is_ma) && (ma->did_print) ) {
1586 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1591 if ( (!strcasecmp(cbtype, "text/plain"))
1592 || (IsEmptyStr(cbtype)) ) {
1595 client_write(wptr, length);
1596 if (wptr[length-1] != '\n') {
1603 if (!strcasecmp(cbtype, "text/html")) {
1604 ptr = html_to_ascii(content, length, 80, 0);
1606 client_write(ptr, wlen);
1607 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1614 if (ma->use_fo_hooks) {
1615 if (PerformFixedOutputHooks(cbtype, content, length)) {
1616 /* above function returns nonzero if it handled the part */
1621 if (strncasecmp(cbtype, "multipart/", 10)) {
1622 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1623 partnum, filename, cbtype, (long)length);
1629 * The client is elegant and sophisticated and wants to be choosy about
1630 * MIME content types, so figure out which multipart/alternative part
1631 * we're going to send.
1633 * We use a system of weights. When we find a part that matches one of the
1634 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1635 * and then set ma->chosen_pref to that MIME type's position in our preference
1636 * list. If we then hit another match, we only replace the first match if
1637 * the preference value is lower.
1639 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1640 void *content, char *cbtype, char *cbcharset, size_t length,
1641 char *encoding, char *cbid, void *cbuserdata)
1643 struct CitContext *CCC = CC;
1648 ma = (struct ma_info *)cbuserdata;
1650 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1651 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1652 // I don't know if there are any side effects! Please TEST TEST TEST
1653 //if (ma->is_ma > 0) {
1655 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1656 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1657 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1658 if (i < ma->chosen_pref) {
1659 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1660 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1661 ma->chosen_pref = i;
1668 * Now that we've chosen our preferred part, output it.
1670 void output_preferred(char *name,
1682 struct CitContext *CCC = CC;
1685 int add_newline = 0;
1688 char *decoded = NULL;
1689 size_t bytes_decoded;
1692 ma = (struct ma_info *)cbuserdata;
1694 /* This is not the MIME part you're looking for... */
1695 if (strcasecmp(partnum, ma->chosen_part)) return;
1697 /* If the content-type of this part is in our preferred formats
1698 * list, we can simply output it verbatim.
1700 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1701 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1702 if (!strcasecmp(buf, cbtype)) {
1703 /* Yeah! Go! W00t!! */
1704 if (ma->dont_decode == 0)
1705 rc = mime_decode_now (content,
1711 break; /* Give us the chance, maybe theres another one. */
1713 if (rc == 0) text_content = (char *)content;
1715 text_content = decoded;
1716 length = bytes_decoded;
1719 if (text_content[length-1] != '\n') {
1722 cprintf("Content-type: %s", cbtype);
1723 if (!IsEmptyStr(cbcharset)) {
1724 cprintf("; charset=%s", cbcharset);
1726 cprintf("\nContent-length: %d\n",
1727 (int)(length + add_newline) );
1728 if (!IsEmptyStr(encoding)) {
1729 cprintf("Content-transfer-encoding: %s\n", encoding);
1732 cprintf("Content-transfer-encoding: 7bit\n");
1734 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1736 if (client_write(text_content, length) == -1)
1738 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1741 if (add_newline) cprintf("\n");
1742 if (decoded != NULL) free(decoded);
1747 /* No translations required or possible: output as text/plain */
1748 cprintf("Content-type: text/plain\n\n");
1750 if (ma->dont_decode == 0)
1751 rc = mime_decode_now (content,
1757 return; /* Give us the chance, maybe theres another one. */
1759 if (rc == 0) text_content = (char *)content;
1761 text_content = decoded;
1762 length = bytes_decoded;
1765 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1766 length, encoding, cbid, cbuserdata);
1767 if (decoded != NULL) free(decoded);
1772 char desired_section[64];
1779 * Callback function for
1781 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1782 void *content, char *cbtype, char *cbcharset, size_t length,
1783 char *encoding, char *cbid, void *cbuserdata)
1785 struct encapmsg *encap;
1787 encap = (struct encapmsg *)cbuserdata;
1789 /* Only proceed if this is the desired section... */
1790 if (!strcasecmp(encap->desired_section, partnum)) {
1791 encap->msglen = length;
1792 encap->msg = malloc(length + 2);
1793 memcpy(encap->msg, content, length);
1800 * Determine whether the specified message exists in the cached_msglist
1801 * (This is a security check)
1803 int check_cached_msglist(long msgnum) {
1804 struct CitContext *CCC = CC;
1806 /* cases in which we skip the check */
1807 if (!CCC) return om_ok; /* not a session */
1808 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1809 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1810 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1813 /* Do a binary search within the cached_msglist for the requested msgnum */
1815 int max = (CC->cached_num_msgs - 1);
1817 while (max >= min) {
1818 int middle = min + (max-min) / 2 ;
1819 if (msgnum == CCC->cached_msglist[middle]) {
1822 if (msgnum > CC->cached_msglist[middle]) {
1830 return om_access_denied;
1835 * Determine whether the currently logged in session has permission to read
1836 * messages in the current room.
1838 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1839 if ( (!(CC->logged_in))
1840 && (!(CC->internal_pgm))
1841 && (!config.c_guest_logins)
1843 return(om_not_logged_in);
1850 * Get a message off disk. (returns om_* values found in msgbase.h)
1853 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1854 int mode, /* how would you like that message? */
1855 int headers_only, /* eschew the message body? */
1856 int do_proto, /* do Citadel protocol responses? */
1857 int crlf, /* Use CRLF newlines instead of LF? */
1858 char *section, /* NULL or a message/rfc822 section */
1859 int flags, /* various flags; see msgbase.h */
1863 struct CitContext *CCC = CC;
1864 struct CtdlMessage *TheMessage = NULL;
1865 int retcode = CIT_OK;
1866 struct encapmsg encap;
1869 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1871 (section ? section : "<>")
1874 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1877 if (r == om_not_logged_in) {
1878 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1881 cprintf("%d An unknown error has occurred.\n", ERROR);
1888 * Check to make sure the message is actually IN this room
1890 r = check_cached_msglist(msg_num);
1891 if (r == om_access_denied) {
1892 /* Not in the cache? We get ONE shot to check it again. */
1893 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1894 r = check_cached_msglist(msg_num);
1897 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1898 msg_num, CCC->room.QRname
1901 if (r == om_access_denied) {
1902 cprintf("%d message %ld was not found in this room\n",
1903 ERROR + HIGHER_ACCESS_REQUIRED,
1912 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1913 * request that we don't even bother loading the body into memory.
1915 if (headers_only == HEADERS_FAST) {
1916 TheMessage = CtdlFetchMessage(msg_num, 0);
1919 TheMessage = CtdlFetchMessage(msg_num, 1);
1922 if (TheMessage == NULL) {
1923 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1924 ERROR + MESSAGE_NOT_FOUND, msg_num);
1925 return(om_no_such_msg);
1928 /* Here is the weird form of this command, to process only an
1929 * encapsulated message/rfc822 section.
1931 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1932 memset(&encap, 0, sizeof encap);
1933 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1934 mime_parser(TheMessage->cm_fields[eMesageText],
1936 *extract_encapsulated_message,
1937 NULL, NULL, (void *)&encap, 0
1940 if ((Author != NULL) && (*Author == NULL))
1942 *Author = TheMessage->cm_fields[eAuthor];
1943 TheMessage->cm_fields[eAuthor] = NULL;
1945 if ((Address != NULL) && (*Address == NULL))
1947 *Address = TheMessage->cm_fields[erFc822Addr];
1948 TheMessage->cm_fields[erFc822Addr] = NULL;
1950 CtdlFreeMessage(TheMessage);
1954 encap.msg[encap.msglen] = 0;
1955 TheMessage = convert_internet_message(encap.msg);
1956 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1958 /* Now we let it fall through to the bottom of this
1959 * function, because TheMessage now contains the
1960 * encapsulated message instead of the top-level
1961 * message. Isn't that neat?
1966 cprintf("%d msg %ld has no part %s\n",
1967 ERROR + MESSAGE_NOT_FOUND,
1971 retcode = om_no_such_msg;
1976 /* Ok, output the message now */
1977 if (retcode == CIT_OK)
1978 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1979 if ((Author != NULL) && (*Author == NULL))
1981 *Author = TheMessage->cm_fields[eAuthor];
1982 TheMessage->cm_fields[eAuthor] = NULL;
1984 if ((Address != NULL) && (*Address == NULL))
1986 *Address = TheMessage->cm_fields[erFc822Addr];
1987 TheMessage->cm_fields[erFc822Addr] = NULL;
1990 CtdlFreeMessage(TheMessage);
1996 char *qp_encode_email_addrs(char *source)
1998 struct CitContext *CCC = CC;
1999 char *user, *node, *name;
2000 const char headerStr[] = "=?UTF-8?Q?";
2004 int need_to_encode = 0;
2010 long nAddrPtrMax = 50;
2015 if (source == NULL) return source;
2016 if (IsEmptyStr(source)) return source;
2017 if (MessageDebugEnabled != 0) cit_backtrace();
2018 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
2020 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
2021 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
2022 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
2025 while (!IsEmptyStr (&source[i])) {
2026 if (nColons >= nAddrPtrMax){
2029 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2030 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
2031 free (AddrPtr), AddrPtr = ptr;
2033 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
2034 memset(&ptr[nAddrPtrMax], 0,
2035 sizeof (long) * nAddrPtrMax);
2037 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
2038 free (AddrUtf8), AddrUtf8 = ptr;
2041 if (((unsigned char) source[i] < 32) ||
2042 ((unsigned char) source[i] > 126)) {
2044 AddrUtf8[nColons] = 1;
2046 if (source[i] == '"')
2047 InQuotes = !InQuotes;
2048 if (!InQuotes && source[i] == ',') {
2049 AddrPtr[nColons] = i;
2054 if (need_to_encode == 0) {
2061 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
2062 Encoded = (char*) malloc (EncodedMaxLen);
2064 for (i = 0; i < nColons; i++)
2065 source[AddrPtr[i]++] = '\0';
2066 /* TODO: if libidn, this might get larger*/
2067 user = malloc(SourceLen + 1);
2068 node = malloc(SourceLen + 1);
2069 name = malloc(SourceLen + 1);
2073 for (i = 0; i < nColons && nPtr != NULL; i++) {
2074 nmax = EncodedMaxLen - (nPtr - Encoded);
2076 process_rfc822_addr(&source[AddrPtr[i]],
2080 /* TODO: libIDN here ! */
2081 if (IsEmptyStr(name)) {
2082 n = snprintf(nPtr, nmax,
2083 (i==0)?"%s@%s" : ",%s@%s",
2087 EncodedName = rfc2047encode(name, strlen(name));
2088 n = snprintf(nPtr, nmax,
2089 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
2090 EncodedName, user, node);
2095 n = snprintf(nPtr, nmax,
2096 (i==0)?"%s" : ",%s",
2097 &source[AddrPtr[i]]);
2103 ptr = (char*) malloc(EncodedMaxLen * 2);
2104 memcpy(ptr, Encoded, EncodedMaxLen);
2105 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
2106 free(Encoded), Encoded = ptr;
2108 i--; /* do it once more with properly lengthened buffer */
2111 for (i = 0; i < nColons; i++)
2112 source[--AddrPtr[i]] = ',';
2123 /* If the last item in a list of recipients was truncated to a partial address,
2124 * remove it completely in order to avoid choking libSieve
2126 void sanitize_truncated_recipient(char *str)
2129 if (num_tokens(str, ',') < 2) return;
2131 int len = strlen(str);
2132 if (len < 900) return;
2133 if (len > 998) str[998] = 0;
2135 char *cptr = strrchr(str, ',');
2138 char *lptr = strchr(cptr, '<');
2139 char *rptr = strchr(cptr, '>');
2141 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2147 void OutputCtdlMsgHeaders(
2148 struct CtdlMessage *TheMessage,
2149 int do_proto) /* do Citadel protocol responses? */
2154 char display_name[256];
2156 /* begin header processing loop for Citadel message format */
2157 safestrncpy(display_name, "<unknown>", sizeof display_name);
2158 if (!CM_IsEmpty(TheMessage, eAuthor)) {
2159 strcpy(buf, TheMessage->cm_fields[eAuthor]);
2160 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2161 safestrncpy(display_name, "****", sizeof display_name);
2163 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2164 safestrncpy(display_name, "anonymous", sizeof display_name);
2167 safestrncpy(display_name, buf, sizeof display_name);
2169 if ((is_room_aide())
2170 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2171 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2172 size_t tmp = strlen(display_name);
2173 snprintf(&display_name[tmp],
2174 sizeof display_name - tmp,
2179 /* Don't show Internet address for users on the
2180 * local Citadel network.
2183 if (!CM_IsEmpty(TheMessage, eNodeName) &&
2184 (haschar(TheMessage->cm_fields[eNodeName], '.') == 0))
2189 /* Now spew the header fields in the order we like them. */
2190 for (i=0; i< NDiskFields; ++i) {
2192 Field = FieldOrder[i];
2193 if (Field != eMesageText) {
2194 if ( (!CM_IsEmpty(TheMessage, Field))
2195 && (msgkeys[Field] != NULL) ) {
2196 if ((Field == eenVelopeTo) ||
2197 (Field == eRecipient) ||
2198 (Field == eCarbonCopY)) {
2199 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
2201 if (Field == eAuthor) {
2202 if (do_proto) cprintf("%s=%s\n",
2206 else if ((Field == erFc822Addr) && (suppress_f)) {
2209 /* Masquerade display name if needed */
2211 if (do_proto) cprintf("%s=%s\n",
2213 TheMessage->cm_fields[Field]
2222 void OutputRFC822MsgHeaders(
2223 struct CtdlMessage *TheMessage,
2224 int flags, /* should the bessage be exported clean */
2226 char *mid, long sizeof_mid,
2227 char *suser, long sizeof_suser,
2228 char *luser, long sizeof_luser,
2229 char *fuser, long sizeof_fuser,
2230 char *snode, long sizeof_snode)
2232 char datestamp[100];
2233 int subject_found = 0;
2240 for (i = 0; i < 256; ++i) {
2241 if (TheMessage->cm_fields[i]) {
2242 mptr = mpptr = TheMessage->cm_fields[i];
2245 safestrncpy(luser, mptr, sizeof_luser);
2246 safestrncpy(suser, mptr, sizeof_suser);
2248 else if (i == 'Y') {
2249 if ((flags & QP_EADDR) != 0) {
2250 mptr = qp_encode_email_addrs(mptr);
2252 sanitize_truncated_recipient(mptr);
2253 cprintf("CC: %s%s", mptr, nl);
2255 else if (i == 'P') {
2256 cprintf("Return-Path: %s%s", mptr, nl);
2258 else if (i == eListID) {
2259 cprintf("List-ID: %s%s", mptr, nl);
2261 else if (i == 'V') {
2262 if ((flags & QP_EADDR) != 0)
2263 mptr = qp_encode_email_addrs(mptr);
2265 while ((*hptr != '\0') && isspace(*hptr))
2267 if (!IsEmptyStr(hptr))
2268 cprintf("Envelope-To: %s%s", hptr, nl);
2270 else if (i == 'U') {
2271 cprintf("Subject: %s%s", mptr, nl);
2275 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2276 else if (i == erFc822Addr)
2277 safestrncpy(fuser, mptr, sizeof_fuser);
2278 /* else if (i == 'O')
2279 cprintf("X-Citadel-Room: %s%s",
2282 safestrncpy(snode, mptr, sizeof_snode);
2285 if (haschar(mptr, '@') == 0)
2287 sanitize_truncated_recipient(mptr);
2288 cprintf("To: %s@%s", mptr, config.c_fqdn);
2293 if ((flags & QP_EADDR) != 0) {
2294 mptr = qp_encode_email_addrs(mptr);
2296 sanitize_truncated_recipient(mptr);
2297 cprintf("To: %s", mptr);
2301 else if (i == 'T') {
2302 datestring(datestamp, sizeof datestamp,
2303 atol(mptr), DATESTRING_RFC822);
2304 cprintf("Date: %s%s", datestamp, nl);
2306 else if (i == 'W') {
2307 cprintf("References: ");
2308 k = num_tokens(mptr, '|');
2309 for (j=0; j<k; ++j) {
2310 extract_token(buf, mptr, j, '|', sizeof buf);
2311 cprintf("<%s>", buf);
2320 else if (i == eReplyTo) {
2322 while ((*hptr != '\0') && isspace(*hptr))
2324 if (!IsEmptyStr(hptr))
2325 cprintf("Reply-To: %s%s", mptr, nl);
2331 if (subject_found == 0) {
2332 cprintf("Subject: (no subject)%s", nl);
2337 void Dump_RFC822HeadersBody(
2338 struct CtdlMessage *TheMessage,
2339 int headers_only, /* eschew the message body? */
2340 int flags, /* should the bessage be exported clean? */
2344 cit_uint8_t prev_ch;
2346 const char *StartOfText = StrBufNOTNULL;
2349 int nllen = strlen(nl);
2352 mptr = TheMessage->cm_fields[eMesageText];
2356 while (*mptr != '\0') {
2357 if (*mptr == '\r') {
2364 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2366 eoh = *(mptr+1) == '\n';
2370 StartOfText = strchr(StartOfText, '\n');
2371 StartOfText = strchr(StartOfText, '\n');
2374 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2375 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2376 ((headers_only != HEADERS_NONE) &&
2377 (headers_only != HEADERS_ONLY))
2379 if (*mptr == '\n') {
2380 memcpy(&outbuf[outlen], nl, nllen);
2382 outbuf[outlen] = '\0';
2385 outbuf[outlen++] = *mptr;
2389 if (flags & ESC_DOT)
2391 if ((prev_ch == '\n') &&
2393 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2395 outbuf[outlen++] = '.';
2400 if (outlen > 1000) {
2401 if (client_write(outbuf, outlen) == -1)
2403 struct CitContext *CCC = CC;
2404 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2411 client_write(outbuf, outlen);
2417 /* If the format type on disk is 1 (fixed-format), then we want
2418 * everything to be output completely literally ... regardless of
2419 * what message transfer format is in use.
2421 void DumpFormatFixed(
2422 struct CtdlMessage *TheMessage,
2423 int mode, /* how would you like that message? */
2430 int nllen = strlen (nl);
2433 mptr = TheMessage->cm_fields[eMesageText];
2435 if (mode == MT_MIME) {
2436 cprintf("Content-type: text/plain\n\n");
2440 while (ch = *mptr++, ch > 0) {
2444 if ((buflen > 250) && (!xlline)){
2448 while ((buflen > 0) &&
2449 (!isspace(buf[buflen])))
2455 mptr -= tbuflen - buflen;
2460 /* if we reach the outer bounds of our buffer,
2461 abort without respect what whe purge. */
2464 (buflen > SIZ - nllen - 2)))
2468 memcpy (&buf[buflen], nl, nllen);
2472 if (client_write(buf, buflen) == -1)
2474 struct CitContext *CCC = CC;
2475 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2487 if (!IsEmptyStr(buf))
2488 cprintf("%s%s", buf, nl);
2492 * Get a message off disk. (returns om_* values found in msgbase.h)
2494 int CtdlOutputPreLoadedMsg(
2495 struct CtdlMessage *TheMessage,
2496 int mode, /* how would you like that message? */
2497 int headers_only, /* eschew the message body? */
2498 int do_proto, /* do Citadel protocol responses? */
2499 int crlf, /* Use CRLF newlines instead of LF? */
2500 int flags /* should the bessage be exported clean? */
2502 struct CitContext *CCC = CC;
2505 const char *nl; /* newline string */
2508 /* Buffers needed for RFC822 translation. These are all filled
2509 * using functions that are bounds-checked, and therefore we can
2510 * make them substantially smaller than SIZ.
2518 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2519 ((TheMessage == NULL) ? "NULL" : "not null"),
2520 mode, headers_only, do_proto, crlf);
2522 strcpy(mid, "unknown");
2523 nl = (crlf ? "\r\n" : "\n");
2525 if (!is_valid_message(TheMessage)) {
2526 MSGM_syslog(LOG_ERR,
2527 "ERROR: invalid preloaded message for output\n");
2529 return(om_no_such_msg);
2532 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2533 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2535 if ( (flags & SUPPRESS_ENV_TO) && (!CM_IsEmpty(TheMessage, eenVelopeTo)) ) {
2536 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2539 /* Are we downloading a MIME component? */
2540 if (mode == MT_DOWNLOAD) {
2541 if (TheMessage->cm_format_type != FMT_RFC822) {
2543 cprintf("%d This is not a MIME message.\n",
2544 ERROR + ILLEGAL_VALUE);
2545 } else if (CCC->download_fp != NULL) {
2546 if (do_proto) cprintf(
2547 "%d You already have a download open.\n",
2548 ERROR + RESOURCE_BUSY);
2550 /* Parse the message text component */
2551 mptr = TheMessage->cm_fields[eMesageText];
2552 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2553 /* If there's no file open by this time, the requested
2554 * section wasn't found, so print an error
2556 if (CCC->download_fp == NULL) {
2557 if (do_proto) cprintf(
2558 "%d Section %s not found.\n",
2559 ERROR + FILE_NOT_FOUND,
2560 CCC->download_desired_section);
2563 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2566 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2567 * in a single server operation instead of opening a download file.
2569 if (mode == MT_SPEW_SECTION) {
2570 if (TheMessage->cm_format_type != FMT_RFC822) {
2572 cprintf("%d This is not a MIME message.\n",
2573 ERROR + ILLEGAL_VALUE);
2575 /* Parse the message text component */
2578 mptr = TheMessage->cm_fields[eMesageText];
2579 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2580 /* If section wasn't found, print an error
2583 if (do_proto) cprintf(
2584 "%d Section %s not found.\n",
2585 ERROR + FILE_NOT_FOUND,
2586 CCC->download_desired_section);
2589 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2592 /* now for the user-mode message reading loops */
2593 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2595 /* Does the caller want to skip the headers? */
2596 if (headers_only == HEADERS_NONE) goto START_TEXT;
2598 /* Tell the client which format type we're using. */
2599 if ( (mode == MT_CITADEL) && (do_proto) ) {
2600 cprintf("type=%d\n", TheMessage->cm_format_type);
2603 /* nhdr=yes means that we're only displaying headers, no body */
2604 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2605 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2608 cprintf("nhdr=yes\n");
2611 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2612 OutputCtdlMsgHeaders(TheMessage, do_proto);
2615 /* begin header processing loop for RFC822 transfer format */
2619 strcpy(snode, NODENAME);
2620 if (mode == MT_RFC822)
2621 OutputRFC822MsgHeaders(
2626 suser, sizeof(suser),
2627 luser, sizeof(luser),
2628 fuser, sizeof(fuser),
2629 snode, sizeof(snode)
2633 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2634 suser[i] = tolower(suser[i]);
2635 if (!isalnum(suser[i])) suser[i]='_';
2638 if (mode == MT_RFC822) {
2639 if (!strcasecmp(snode, NODENAME)) {
2640 safestrncpy(snode, FQDN, sizeof snode);
2643 /* Construct a fun message id */
2644 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2645 if (strchr(mid, '@')==NULL) {
2646 cprintf("@%s", snode);
2650 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2651 cprintf("From: \"----\" <x@x.org>%s", nl);
2653 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2654 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2656 else if (!IsEmptyStr(fuser)) {
2657 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2660 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2663 /* Blank line signifying RFC822 end-of-headers */
2664 if (TheMessage->cm_format_type != FMT_RFC822) {
2669 /* end header processing loop ... at this point, we're in the text */
2671 if (headers_only == HEADERS_FAST) goto DONE;
2673 /* Tell the client about the MIME parts in this message */
2674 if (TheMessage->cm_format_type == FMT_RFC822) {
2675 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2676 mptr = TheMessage->cm_fields[eMesageText];
2677 memset(&ma, 0, sizeof(struct ma_info));
2678 mime_parser(mptr, NULL,
2679 (do_proto ? *list_this_part : NULL),
2680 (do_proto ? *list_this_pref : NULL),
2681 (do_proto ? *list_this_suff : NULL),
2684 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2685 Dump_RFC822HeadersBody(
2694 if (headers_only == HEADERS_ONLY) {
2698 /* signify start of msg text */
2699 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2700 if (do_proto) cprintf("text\n");
2703 if (TheMessage->cm_format_type == FMT_FIXED)
2706 mode, /* how would you like that message? */
2709 /* If the message on disk is format 0 (Citadel vari-format), we
2710 * output using the formatter at 80 columns. This is the final output
2711 * form if the transfer format is RFC822, but if the transfer format
2712 * is Citadel proprietary, it'll still work, because the indentation
2713 * for new paragraphs is correct and the client will reformat the
2714 * message to the reader's screen width.
2716 if (TheMessage->cm_format_type == FMT_CITADEL) {
2717 mptr = TheMessage->cm_fields[eMesageText];
2719 if (mode == MT_MIME) {
2720 cprintf("Content-type: text/x-citadel-variformat\n\n");
2725 /* If the message on disk is format 4 (MIME), we've gotta hand it
2726 * off to the MIME parser. The client has already been told that
2727 * this message is format 1 (fixed format), so the callback function
2728 * we use will display those parts as-is.
2730 if (TheMessage->cm_format_type == FMT_RFC822) {
2731 memset(&ma, 0, sizeof(struct ma_info));
2733 if (mode == MT_MIME) {
2734 ma.use_fo_hooks = 0;
2735 strcpy(ma.chosen_part, "1");
2736 ma.chosen_pref = 9999;
2737 ma.dont_decode = CCC->msg4_dont_decode;
2738 mime_parser(mptr, NULL,
2739 *choose_preferred, *fixed_output_pre,
2740 *fixed_output_post, (void *)&ma, 1);
2741 mime_parser(mptr, NULL,
2742 *output_preferred, NULL, NULL, (void *)&ma, 1);
2745 ma.use_fo_hooks = 1;
2746 mime_parser(mptr, NULL,
2747 *fixed_output, *fixed_output_pre,
2748 *fixed_output_post, (void *)&ma, 0);
2753 DONE: /* now we're done */
2754 if (do_proto) cprintf("000\n");
2760 * display a message (mode 0 - Citadel proprietary)
2762 void cmd_msg0(char *cmdbuf)
2765 int headers_only = HEADERS_ALL;
2767 msgid = extract_long(cmdbuf, 0);
2768 headers_only = extract_int(cmdbuf, 1);
2770 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2776 * display a message (mode 2 - RFC822)
2778 void cmd_msg2(char *cmdbuf)
2781 int headers_only = HEADERS_ALL;
2783 msgid = extract_long(cmdbuf, 0);
2784 headers_only = extract_int(cmdbuf, 1);
2786 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2792 * display a message (mode 3 - IGnet raw format - internal programs only)
2794 void cmd_msg3(char *cmdbuf)
2797 struct CtdlMessage *msg = NULL;
2800 if (CC->internal_pgm == 0) {
2801 cprintf("%d This command is for internal programs only.\n",
2802 ERROR + HIGHER_ACCESS_REQUIRED);
2806 msgnum = extract_long(cmdbuf, 0);
2807 msg = CtdlFetchMessage(msgnum, 1);
2809 cprintf("%d Message %ld not found.\n",
2810 ERROR + MESSAGE_NOT_FOUND, msgnum);
2814 serialize_message(&smr, msg);
2815 CtdlFreeMessage(msg);
2818 cprintf("%d Unable to serialize message\n",
2819 ERROR + INTERNAL_ERROR);
2823 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2824 client_write((char *)smr.ser, (int)smr.len);
2831 * Display a message using MIME content types
2833 void cmd_msg4(char *cmdbuf)
2838 msgid = extract_long(cmdbuf, 0);
2839 extract_token(section, cmdbuf, 1, '|', sizeof section);
2840 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2846 * Client tells us its preferred message format(s)
2848 void cmd_msgp(char *cmdbuf)
2850 if (!strcasecmp(cmdbuf, "dont_decode")) {
2851 CC->msg4_dont_decode = 1;
2852 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2855 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2856 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2862 * Open a component of a MIME message as a download file
2864 void cmd_opna(char *cmdbuf)
2867 char desired_section[128];
2869 msgid = extract_long(cmdbuf, 0);
2870 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2871 safestrncpy(CC->download_desired_section, desired_section,
2872 sizeof CC->download_desired_section);
2873 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2878 * Open a component of a MIME message and transmit it all at once
2880 void cmd_dlat(char *cmdbuf)
2883 char desired_section[128];
2885 msgid = extract_long(cmdbuf, 0);
2886 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2887 safestrncpy(CC->download_desired_section, desired_section,
2888 sizeof CC->download_desired_section);
2889 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2894 * Save one or more message pointers into a specified room
2895 * (Returns 0 for success, nonzero for failure)
2896 * roomname may be NULL to use the current room
2898 * Note that the 'supplied_msg' field may be set to NULL, in which case
2899 * the message will be fetched from disk, by number, if we need to perform
2900 * replication checks. This adds an additional database read, so if the
2901 * caller already has the message in memory then it should be supplied. (Obviously
2902 * this mode of operation only works if we're saving a single message.)
2904 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2905 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2907 struct CitContext *CCC = CC;
2909 char hold_rm[ROOMNAMELEN];
2910 struct cdbdata *cdbfr;
2913 long highest_msg = 0L;
2916 struct CtdlMessage *msg = NULL;
2918 long *msgs_to_be_merged = NULL;
2919 int num_msgs_to_be_merged = 0;
2921 MSG_syslog(LOG_DEBUG,
2922 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2923 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2926 strcpy(hold_rm, CCC->room.QRname);
2929 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2930 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2931 if (num_newmsgs > 1) supplied_msg = NULL;
2933 /* Now the regular stuff */
2934 if (CtdlGetRoomLock(&CCC->room,
2935 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2937 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2938 return(ERROR + ROOM_NOT_FOUND);
2942 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2943 num_msgs_to_be_merged = 0;
2946 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2947 if (cdbfr == NULL) {
2951 msglist = (long *) cdbfr->ptr;
2952 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2953 num_msgs = cdbfr->len / sizeof(long);
2958 /* Create a list of msgid's which were supplied by the caller, but do
2959 * not already exist in the target room. It is absolutely taboo to
2960 * have more than one reference to the same message in a room.
2962 for (i=0; i<num_newmsgs; ++i) {
2964 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2965 if (msglist[j] == newmsgidlist[i]) {
2970 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2974 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2977 * Now merge the new messages
2979 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2980 if (msglist == NULL) {
2981 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2982 free(msgs_to_be_merged);
2983 return (ERROR + INTERNAL_ERROR);
2985 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2986 num_msgs += num_msgs_to_be_merged;
2988 /* Sort the message list, so all the msgid's are in order */
2989 num_msgs = sort_msglist(msglist, num_msgs);
2991 /* Determine the highest message number */
2992 highest_msg = msglist[num_msgs - 1];
2994 /* Write it back to disk. */
2995 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2996 msglist, (int)(num_msgs * sizeof(long)));
2998 /* Free up the memory we used. */
3001 /* Update the highest-message pointer and unlock the room. */
3002 CCC->room.QRhighest = highest_msg;
3003 CtdlPutRoomLock(&CCC->room);
3005 /* Perform replication checks if necessary */
3006 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
3007 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
3009 for (i=0; i<num_msgs_to_be_merged; ++i) {
3010 msgid = msgs_to_be_merged[i];
3012 if (supplied_msg != NULL) {
3016 msg = CtdlFetchMessage(msgid, 0);
3020 ReplicationChecks(msg);
3022 /* If the message has an Exclusive ID, index that... */
3023 if (!CM_IsEmpty(msg, eExclusiveID)) {
3024 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
3027 /* Free up the memory we may have allocated */
3028 if (msg != supplied_msg) {
3029 CtdlFreeMessage(msg);
3037 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
3040 /* Submit this room for processing by hooks */
3041 PerformRoomHooks(&CCC->room);
3043 /* Go back to the room we were in before we wandered here... */
3044 CtdlGetRoom(&CCC->room, hold_rm);
3046 /* Bump the reference count for all messages which were merged */
3047 if (!suppress_refcount_adj) {
3048 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
3051 /* Free up memory... */
3052 if (msgs_to_be_merged != NULL) {
3053 free(msgs_to_be_merged);
3056 /* Return success. */
3062 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
3065 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
3066 int do_repl_check, struct CtdlMessage *supplied_msg)
3068 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
3075 * Message base operation to save a new message to the message store
3076 * (returns new message number)
3078 * This is the back end for CtdlSubmitMsg() and should not be directly
3079 * called by server-side modules.
3082 long send_message(struct CtdlMessage *msg) {
3083 struct CitContext *CCC = CC;
3092 /* Get a new message number */
3093 newmsgid = get_new_message_number();
3094 msgidbuflen = snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
3095 (long unsigned int) time(NULL),
3096 (long unsigned int) newmsgid,
3100 /* Generate an ID if we don't have one already */
3101 if (CM_IsEmpty(msg, emessageId)) {
3102 CM_SetField(msg, emessageId, msgidbuf, msgidbuflen);
3105 /* If the message is big, set its body aside for storage elsewhere */
3106 if (!CM_IsEmpty(msg, eMesageText)) {
3107 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
3109 holdM = msg->cm_fields[eMesageText];
3110 msg->cm_fields[eMesageText] = NULL;
3114 /* Serialize our data structure for storage in the database */
3115 serialize_message(&smr, msg);
3118 msg->cm_fields[eMesageText] = holdM;
3122 cprintf("%d Unable to serialize message\n",
3123 ERROR + INTERNAL_ERROR);
3127 /* Write our little bundle of joy into the message base */
3128 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3129 smr.ser, smr.len) < 0) {
3130 MSGM_syslog(LOG_ERR, "Can't store message\n");
3134 cdb_store(CDB_BIGMSGS,
3144 /* Free the memory we used for the serialized message */
3147 /* Return the *local* message ID to the caller
3148 * (even if we're storing an incoming network message)
3156 * Serialize a struct CtdlMessage into the format used on disk and network.
3158 * This function loads up a "struct ser_ret" (defined in server.h) which
3159 * contains the length of the serialized message and a pointer to the
3160 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3162 void serialize_message(struct ser_ret *ret, /* return values */
3163 struct CtdlMessage *msg) /* unserialized msg */
3165 struct CitContext *CCC = CC;
3166 size_t wlen, fieldlen;
3168 long lengths[NDiskFields];
3170 memset(lengths, 0, sizeof(lengths));
3173 * Check for valid message format
3175 if (is_valid_message(msg) == 0) {
3176 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3183 for (i=0; i < NDiskFields; ++i)
3184 if (msg->cm_fields[FieldOrder[i]] != NULL)
3186 lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
3187 ret->len += lengths[i] + 2;
3190 ret->ser = malloc(ret->len);
3191 if (ret->ser == NULL) {
3192 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3193 (long)ret->len, strerror(errno));
3200 ret->ser[1] = msg->cm_anon_type;
3201 ret->ser[2] = msg->cm_format_type;
3204 for (i=0; i < NDiskFields; ++i)
3205 if (msg->cm_fields[FieldOrder[i]] != NULL)
3207 fieldlen = lengths[i];
3208 ret->ser[wlen++] = (char)FieldOrder[i];
3210 memcpy(&ret->ser[wlen],
3211 msg->cm_fields[FieldOrder[i]],
3214 wlen = wlen + fieldlen + 1;
3217 if (ret->len != wlen) {
3218 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3219 (long)ret->len, (long)wlen);
3227 * Check to see if any messages already exist in the current room which
3228 * carry the same Exclusive ID as this one. If any are found, delete them.
3230 void ReplicationChecks(struct CtdlMessage *msg) {
3231 struct CitContext *CCC = CC;
3232 long old_msgnum = (-1L);
3234 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3236 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3239 /* No exclusive id? Don't do anything. */
3240 if (msg == NULL) return;
3241 if (CM_IsEmpty(msg, eExclusiveID)) return;
3243 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3244 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
3246 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
3247 if (old_msgnum > 0L) {
3248 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3249 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3256 * Save a message to disk and submit it into the delivery system.
3258 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3259 struct recptypes *recps, /* recipients (if mail) */
3260 const char *force, /* force a particular room? */
3261 int flags /* should the message be exported clean? */
3264 char submit_filename[128];
3265 char hold_rm[ROOMNAMELEN];
3266 char actual_rm[ROOMNAMELEN];
3267 char force_room[ROOMNAMELEN];
3268 char content_type[SIZ]; /* We have to learn this */
3269 char recipient[SIZ];
3272 const char *mptr = NULL;
3273 struct ctdluser userbuf;
3275 struct MetaData smi;
3276 FILE *network_fp = NULL;
3277 static int seqnum = 1;
3278 struct CtdlMessage *imsg = NULL;
3280 size_t instr_alloc = 0;
3282 char *hold_R, *hold_D;
3283 char *collected_addresses = NULL;
3284 struct addresses_to_be_filed *aptr = NULL;
3285 StrBuf *saved_rfc822_version = NULL;
3286 int qualified_for_journaling = 0;
3287 CitContext *CCC = MyContext();
3288 char bounce_to[1024] = "";
3291 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3292 if (is_valid_message(msg) == 0) return(-1); /* self check */
3294 /* If this message has no timestamp, we take the liberty of
3295 * giving it one, right now.
3297 if (CM_IsEmpty(msg, eTimestamp)) {
3298 CM_SetFieldLONG(msg, eTimestamp, time(NULL));
3301 /* If this message has no path, we generate one.
3303 if (CM_IsEmpty(msg, eMessagePath)) {
3304 if (!CM_IsEmpty(msg, eAuthor)) {
3305 CM_CopyField(msg, eMessagePath, eAuthor);
3306 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
3307 if (isspace(msg->cm_fields[eMessagePath][a])) {
3308 msg->cm_fields[eMessagePath][a] = ' ';
3313 CM_SetField(msg, eMessagePath, HKEY("unknown"));
3317 if (force == NULL) {
3318 force_room[0] = '\0';
3321 strcpy(force_room, force);
3324 /* Learn about what's inside, because it's what's inside that counts */
3325 if (CM_IsEmpty(msg, eMesageText)) {
3326 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3330 switch (msg->cm_format_type) {
3332 strcpy(content_type, "text/x-citadel-variformat");
3335 strcpy(content_type, "text/plain");
3338 strcpy(content_type, "text/plain");
3339 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3342 safestrncpy(content_type, &mptr[13], sizeof content_type);
3343 striplt(content_type);
3344 aptr = content_type;
3345 while (!IsEmptyStr(aptr)) {
3357 /* Goto the correct room */
3358 room = (recps) ? CCC->room.QRname : SENTITEMS;
3359 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3360 strcpy(hold_rm, CCC->room.QRname);
3361 strcpy(actual_rm, CCC->room.QRname);
3362 if (recps != NULL) {
3363 strcpy(actual_rm, SENTITEMS);
3366 /* If the user is a twit, move to the twit room for posting */
3368 if (CCC->user.axlevel == AxProbU) {
3369 strcpy(hold_rm, actual_rm);
3370 strcpy(actual_rm, config.c_twitroom);
3371 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3375 /* ...or if this message is destined for Aide> then go there. */
3376 if (!IsEmptyStr(force_room)) {
3377 strcpy(actual_rm, force_room);
3380 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3381 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3382 /* CtdlGetRoom(&CCC->room, actual_rm); */
3383 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3387 * If this message has no O (room) field, generate one.
3389 if (CM_IsEmpty(msg, eOriginalRoom)) {
3390 CM_SetField(msg, eOriginalRoom, CCC->room.QRname, strlen(CCC->room.QRname));
3393 /* Perform "before save" hooks (aborting if any return nonzero) */
3394 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3395 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3398 * If this message has an Exclusive ID, and the room is replication
3399 * checking enabled, then do replication checks.
3401 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3402 ReplicationChecks(msg);
3405 /* Save it to disk */
3406 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3407 newmsgid = send_message(msg);
3408 if (newmsgid <= 0L) return(-5);
3410 /* Write a supplemental message info record. This doesn't have to
3411 * be a critical section because nobody else knows about this message
3414 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3415 memset(&smi, 0, sizeof(struct MetaData));
3416 smi.meta_msgnum = newmsgid;
3417 smi.meta_refcount = 0;
3418 safestrncpy(smi.meta_content_type, content_type,
3419 sizeof smi.meta_content_type);
3422 * Measure how big this message will be when rendered as RFC822.
3423 * We do this for two reasons:
3424 * 1. We need the RFC822 length for the new metadata record, so the
3425 * POP and IMAP services don't have to calculate message lengths
3426 * while the user is waiting (multiplied by potentially hundreds
3427 * or thousands of messages).
3428 * 2. If journaling is enabled, we will need an RFC822 version of the
3429 * message to attach to the journalized copy.
3431 if (CCC->redirect_buffer != NULL) {
3432 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3435 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3436 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3437 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3438 saved_rfc822_version = CCC->redirect_buffer;
3439 CCC->redirect_buffer = NULL;
3443 /* Now figure out where to store the pointers */
3444 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3446 /* If this is being done by the networker delivering a private
3447 * message, we want to BYPASS saving the sender's copy (because there
3448 * is no local sender; it would otherwise go to the Trashcan).
3450 if ((!CCC->internal_pgm) || (recps == NULL)) {
3451 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3452 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3453 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3457 /* For internet mail, drop a copy in the outbound queue room */
3458 if ((recps != NULL) && (recps->num_internet > 0)) {
3459 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3462 /* If other rooms are specified, drop them there too. */
3463 if ((recps != NULL) && (recps->num_room > 0))
3464 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3465 extract_token(recipient, recps->recp_room, i,
3466 '|', sizeof recipient);
3467 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3468 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3471 /* Bump this user's messages posted counter. */
3472 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3473 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3474 CCC->user.posted = CCC->user.posted + 1;
3475 CtdlPutUserLock(&CCC->user);
3477 /* Decide where bounces need to be delivered */
3478 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3479 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3481 else if (CCC->logged_in) {
3482 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3485 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3488 /* If this is private, local mail, make a copy in the
3489 * recipient's mailbox and bump the reference count.
3491 if ((recps != NULL) && (recps->num_local > 0))
3492 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3494 recipientlen = extract_token(recipient,
3495 recps->recp_local, i,
3496 '|', sizeof recipient);
3497 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3499 if (CtdlGetUser(&userbuf, recipient) == 0) {
3500 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3501 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3502 CtdlBumpNewMailCounter(userbuf.usernum);
3503 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3504 /* Generate a instruction message for the Funambol notification
3505 * server, in the same style as the SMTP queue
3509 instr = malloc(instr_alloc);
3510 instrlen = snprintf(
3512 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3516 (long)time(NULL), //todo: time() is expensive!
3520 imsg = malloc(sizeof(struct CtdlMessage));
3521 memset(imsg, 0, sizeof(struct CtdlMessage));
3522 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3523 imsg->cm_anon_type = MES_NORMAL;
3524 imsg->cm_format_type = FMT_RFC822;
3525 CM_SetField(imsg, eMsgSubject, HKEY("QMSG"));
3526 CM_SetField(imsg, eAuthor, HKEY("Citadel"));
3527 CM_SetField(imsg, eJournal, HKEY("do not journal"));
3528 CM_SetAsField(imsg, eMesageText, &instr, instrlen);
3529 CM_SetField(imsg, eExtnotify, recipient, recipientlen);
3530 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3531 CtdlFreeMessage(imsg);
3535 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3536 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3540 /* Perform "after save" hooks */
3541 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3543 CM_SetFieldLONG(msg, eVltMsgNum, newmsgid);
3544 PerformMessageHooks(msg, EVT_AFTERSAVE);
3545 CM_FlushField(msg, eVltMsgNum);
3547 /* For IGnet mail, we have to save a new copy into the spooler for
3548 * each recipient, with the R and D fields set to the recipient and
3549 * destination-node. This has two ugly side effects: all other
3550 * recipients end up being unlisted in this recipient's copy of the
3551 * message, and it has to deliver multiple messages to the same
3552 * node. We'll revisit this again in a year or so when everyone has
3553 * a network spool receiver that can handle the new style messages.
3555 if ((recps != NULL) && (recps->num_ignet > 0))
3556 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3557 extract_token(recipient, recps->recp_ignet, i,
3558 '|', sizeof recipient);
3560 hold_R = msg->cm_fields[eRecipient];
3561 hold_D = msg->cm_fields[eDestination];
3562 msg->cm_fields[eRecipient] = malloc(SIZ);
3563 msg->cm_fields[eDestination] = malloc(128);
3564 extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3565 extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3567 serialize_message(&smr, msg);
3569 snprintf(submit_filename, sizeof submit_filename,
3570 "%s/netmail.%04lx.%04x.%04x",
3572 (long) getpid(), CCC->cs_pid, ++seqnum);
3573 network_fp = fopen(submit_filename, "wb+");
3574 if (network_fp != NULL) {
3575 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3577 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3585 free(msg->cm_fields[eRecipient]);
3586 free(msg->cm_fields[eDestination]);
3587 msg->cm_fields[eRecipient] = hold_R;
3588 msg->cm_fields[eDestination] = hold_D;
3591 /* Go back to the room we started from */
3592 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3593 if (strcasecmp(hold_rm, CCC->room.QRname))
3594 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3596 /* For internet mail, generate delivery instructions.
3597 * Yes, this is recursive. Deal with it. Infinite recursion does
3598 * not happen because the delivery instructions message does not
3599 * contain a recipient.
3601 if ((recps != NULL) && (recps->num_internet > 0)) {
3602 StrBuf *SpoolMsg = NewStrBuf();
3605 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3607 StrBufPrintf(SpoolMsg,
3608 "Content-type: "SPOOLMIME"\n"
3617 if (recps->envelope_from != NULL) {
3618 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3619 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3620 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3622 if (recps->sending_room != NULL) {
3623 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3624 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3625 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3628 nTokens = num_tokens(recps->recp_internet, '|');
3629 for (i = 0; i < nTokens; i++) {
3631 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3633 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3634 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3635 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3639 imsg = malloc(sizeof(struct CtdlMessage));
3640 memset(imsg, 0, sizeof(struct CtdlMessage));
3641 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3642 imsg->cm_anon_type = MES_NORMAL;
3643 imsg->cm_format_type = FMT_RFC822;
3644 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3645 imsg->cm_fields[eAuthor] = strdup("Citadel");
3646 imsg->cm_fields[eJournal] = strdup("do not journal");
3647 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3648 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3649 CtdlFreeMessage(imsg);
3653 * Any addresses to harvest for someone's address book?
3655 if ( (CCC->logged_in) && (recps != NULL) ) {
3656 collected_addresses = harvest_collected_addresses(msg);
3659 if (collected_addresses != NULL) {
3660 aptr = (struct addresses_to_be_filed *)
3661 malloc(sizeof(struct addresses_to_be_filed));
3662 CtdlMailboxName(actual_rm, sizeof actual_rm,
3663 &CCC->user, USERCONTACTSROOM);
3664 aptr->roomname = strdup(actual_rm);
3665 aptr->collected_addresses = collected_addresses;
3666 begin_critical_section(S_ATBF);
3669 end_critical_section(S_ATBF);
3673 * Determine whether this message qualifies for journaling.
3675 if (!CM_IsEmpty(msg, eJournal)) {
3676 qualified_for_journaling = 0;
3679 if (recps == NULL) {
3680 qualified_for_journaling = config.c_journal_pubmsgs;
3682 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3683 qualified_for_journaling = config.c_journal_email;
3686 qualified_for_journaling = config.c_journal_pubmsgs;
3691 * Do we have to perform journaling? If so, hand off the saved
3692 * RFC822 version will be handed off to the journaler for background
3693 * submit. Otherwise, we have to free the memory ourselves.
3695 if (saved_rfc822_version != NULL) {
3696 if (qualified_for_journaling) {
3697 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3700 FreeStrBuf(&saved_rfc822_version);
3710 * Convenience function for generating small administrative messages.
3712 void quickie_message(const char *from,
3713 const char *fromaddr,
3718 const char *subject)
3720 struct CtdlMessage *msg;
3721 struct recptypes *recp = NULL;
3723 msg = malloc(sizeof(struct CtdlMessage));
3724 memset(msg, 0, sizeof(struct CtdlMessage));
3725 msg->cm_magic = CTDLMESSAGE_MAGIC;
3726 msg->cm_anon_type = MES_NORMAL;
3727 msg->cm_format_type = format_type;
3730 msg->cm_fields[eAuthor] = strdup(from);
3732 else if (fromaddr != NULL) {
3733 msg->cm_fields[eAuthor] = strdup(fromaddr);
3734 if (strchr(msg->cm_fields[eAuthor], '@')) {
3735 *strchr(msg->cm_fields[eAuthor], '@') = 0;
3739 msg->cm_fields[eAuthor] = strdup("Citadel");
3742 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3743 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3744 msg->cm_fields[eNodeName] = strdup(NODENAME);
3746 msg->cm_fields[eRecipient] = strdup(to);
3747 recp = validate_recipients(to, NULL, 0);
3749 if (subject != NULL) {
3750 msg->cm_fields[eMsgSubject] = strdup(subject);
3752 msg->cm_fields[eMesageText] = strdup(text);
3754 CtdlSubmitMsg(msg, recp, room, 0);
3755 CtdlFreeMessage(msg);
3756 if (recp != NULL) free_recipients(recp);
3759 void flood_protect_quickie_message(const char *from,
3760 const char *fromaddr,
3765 const char *subject,
3767 const char **CritStr,
3774 u_char rawdigest[MD5_DIGEST_LEN];
3775 struct MD5Context md5context;
3779 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3781 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3782 MD5Init(&md5context);
3784 for (i = 0; i < nCriterions; i++)
3785 MD5Update(&md5context,
3786 (const unsigned char*)CritStr[i], CritStrLen[i]);
3787 MD5Update(&md5context,
3788 (const unsigned char*)timestamp, tslen);
3789 MD5Final(rawdigest, &md5context);
3791 guid = NewStrBufPlain(NULL,
3792 MD5_DIGEST_LEN * 2 + 12);
3793 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3794 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3795 if (StrLength(guid) > 40)
3796 StrBufCutAt(guid, 40, NULL);
3798 if (CheckIfAlreadySeen("FPAideMessage",
3807 /* yes, we did. flood protection kicks in. */
3809 "not sending message again\n");
3813 /* no, this message isn't sent recently; go ahead. */
3814 quickie_message(from,
3825 * Back end function used by CtdlMakeMessage() and similar functions
3827 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3829 size_t maxlen, /* maximum message length */
3830 StrBuf *exist, /* if non-null, append to it;
3831 exist is ALWAYS freed */
3832 int crlf, /* CRLF newlines instead of LF */
3833 int *sock /* socket handle or 0 for this session's client socket */
3842 LineBuf = NewStrBufPlain(NULL, SIZ);
3843 if (exist == NULL) {
3844 Message = NewStrBufPlain(NULL, 4 * SIZ);
3847 Message = NewStrBufDup(exist);
3850 /* Do we need to change leading ".." to "." for SMTP escaping? */
3851 if ((tlen == 1) && (*terminator == '.')) {
3855 /* read in the lines of message text one by one */
3858 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3863 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3865 if ((StrLength(LineBuf) == tlen) &&
3866 (!strcmp(ChrPtr(LineBuf), terminator)))
3869 if ( (!flushing) && (!finished) ) {
3871 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3874 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3877 /* Unescape SMTP-style input of two dots at the beginning of the line */
3879 (StrLength(LineBuf) == 2) &&
3880 (!strcmp(ChrPtr(LineBuf), "..")))
3882 StrBufCutLeft(LineBuf, 1);
3885 StrBufAppendBuf(Message, LineBuf, 0);
3888 /* if we've hit the max msg length, flush the rest */
3889 if (StrLength(Message) >= maxlen) flushing = 1;
3891 } while (!finished);
3892 FreeStrBuf(&LineBuf);
3896 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3900 FreeStrBuf(&(*Msg)->MsgBuf);
3906 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3908 size_t maxlen, /* maximum message length */
3909 size_t expectlen, /* if we expect a message, how long should it be? */
3910 StrBuf *exist, /* if non-null, append to it;
3911 exist is ALWAYS freed */
3912 long eLen, /* length of exist */
3913 int crlf /* CRLF newlines instead of LF */
3916 ReadAsyncMsg *NewMsg;
3918 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3919 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3921 if (exist == NULL) {
3924 if (expectlen == 0) {
3928 len = expectlen + 10;
3930 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3933 NewMsg->MsgBuf = NewStrBufDup(exist);
3935 /* Do we need to change leading ".." to "." for SMTP escaping? */
3936 if ((tlen == 1) && (*terminator == '.')) {
3940 NewMsg->terminator = terminator;
3941 NewMsg->tlen = tlen;
3943 NewMsg->maxlen = maxlen;
3945 NewMsg->crlf = crlf;
3951 * Back end function used by CtdlMakeMessage() and similar functions
3953 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3955 ReadAsyncMsg *ReadMsg;
3956 int MsgFinished = 0;
3957 eReadState Finished = eMustReadMore;
3962 const char *pch = ChrPtr(IO->SendBuf.Buf);
3963 const char *pchh = IO->SendBuf.ReadWritePointer;
3969 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3970 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3971 ((CitContext*)(IO->CitContext))->ServiceName,
3974 fd = fopen(fn, "a+");
3977 ReadMsg = IO->ReadMsg;
3979 /* read in the lines of message text one by one */
3981 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3984 case eMustReadMore: /// read new from socket...
3986 if (IO->RecvBuf.ReadWritePointer != NULL) {
3987 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3988 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3990 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3994 fprintf(fd, "BufferEmpty! \n");
4000 case eBufferNotEmpty: /* shouldn't happen... */
4001 case eReadSuccess: /// done for now...
4003 case eReadFail: /// WHUT?
4009 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
4010 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
4013 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
4016 else if (!ReadMsg->flushing) {
4019 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
4022 /* Unescape SMTP-style input of two dots at the beginning of the line */
4023 if ((ReadMsg->dodot) &&
4024 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
4025 (!strcmp(ChrPtr(IO->IOBuf), "..")))
4028 fprintf(fd, "UnEscaped!\n");
4030 StrBufCutLeft(IO->IOBuf, 1);
4033 if (ReadMsg->crlf) {
4034 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
4037 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
4040 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
4043 /* if we've hit the max msg length, flush the rest */
4044 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
4046 } while (!MsgFinished);
4049 fprintf(fd, "Done with reading; %s.\n, ",
4050 (MsgFinished)?"Message Finished": "FAILED");
4054 return eReadSuccess;
4061 * Back end function used by CtdlMakeMessage() and similar functions
4063 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
4065 size_t maxlen, /* maximum message length */
4066 StrBuf *exist, /* if non-null, append to it;
4067 exist is ALWAYS freed */
4068 int crlf, /* CRLF newlines instead of LF */
4069 int *sock /* socket handle or 0 for this session's client socket */
4074 Message = CtdlReadMessageBodyBuf(terminator,
4080 if (Message == NULL)
4083 return SmashStrBuf(&Message);
4088 * Build a binary message to be saved on disk.
4089 * (NOTE: if you supply 'preformatted_text', the buffer you give it
4090 * will become part of the message. This means you are no longer
4091 * responsible for managing that memory -- it will be freed along with
4092 * the rest of the fields when CtdlFreeMessage() is called.)
4095 struct CtdlMessage *CtdlMakeMessage(
4096 struct ctdluser *author, /* author's user structure */
4097 char *recipient, /* NULL if it's not mail */
4098 char *recp_cc, /* NULL if it's not mail */
4099 char *room, /* room where it's going */
4100 int type, /* see MES_ types in header file */
4101 int format_type, /* variformat, plain text, MIME... */
4102 char *fake_name, /* who we're masquerading as */
4103 char *my_email, /* which of my email addresses to use (empty is ok) */
4104 char *subject, /* Subject (optional) */
4105 char *supplied_euid, /* ...or NULL if this is irrelevant */
4106 char *preformatted_text, /* ...or NULL to read text from client */
4107 char *references /* Thread references */
4109 char dest_node[256];
4111 struct CtdlMessage *msg;
4113 StrBuf *FakeEncAuthor = NULL;
4115 msg = malloc(sizeof(struct CtdlMessage));
4116 memset(msg, 0, sizeof(struct CtdlMessage));
4117 msg->cm_magic = CTDLMESSAGE_MAGIC;
4118 msg->cm_anon_type = type;
4119 msg->cm_format_type = format_type;
4121 /* Don't confuse the poor folks if it's not routed mail. */
4122 strcpy(dest_node, "");
4124 if (recipient != NULL) striplt(recipient);
4125 if (recp_cc != NULL) striplt(recp_cc);
4127 /* Path or Return-Path */
4128 if (my_email == NULL) my_email = "";
4130 if (!IsEmptyStr(my_email)) {
4131 msg->cm_fields[eMessagePath] = strdup(my_email);
4134 snprintf(buf, sizeof buf, "%s", author->fullname);
4135 msg->cm_fields[eMessagePath] = strdup(buf);
4137 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
4139 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
4140 msg->cm_fields[eTimestamp] = strdup(buf);
4142 if ((fake_name != NULL) && (fake_name[0])) { /* author */
4143 FakeAuthor = NewStrBufPlain (fake_name, -1);
4146 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4148 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4149 msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
4150 FreeStrBuf(&FakeAuthor);
4152 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4153 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
4156 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
4159 msg->cm_fields[eNodeName] = strdup(NODENAME); /* nodename */
4160 msg->cm_fields[eHumanNode] = strdup(HUMANNODE); /* hnodename */
4162 if ((recipient != NULL) && (recipient[0] != 0)) {
4163 msg->cm_fields[eRecipient] = strdup(recipient);
4165 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4166 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
4168 if (dest_node[0] != 0) {
4169 msg->cm_fields[eDestination] = strdup(dest_node);
4172 if (!IsEmptyStr(my_email)) {
4173 msg->cm_fields[erFc822Addr] = strdup(my_email);
4175 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4176 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
4179 if (subject != NULL) {
4182 length = strlen(subject);
4188 while ((subject[i] != '\0') &&
4189 (IsAscii = isascii(subject[i]) != 0 ))
4192 msg->cm_fields[eMsgSubject] = strdup(subject);
4193 else /* ok, we've got utf8 in the string. */
4195 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
4201 if (supplied_euid != NULL) {
4202 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
4205 if ((references != NULL) && (!IsEmptyStr(references))) {
4206 if (msg->cm_fields[eWeferences] != NULL)
4207 free(msg->cm_fields[eWeferences]);
4208 msg->cm_fields[eWeferences] = strdup(references);
4211 if (preformatted_text != NULL) {
4212 msg->cm_fields[eMesageText] = preformatted_text;
4215 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4222 * Check to see whether we have permission to post a message in the current
4223 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4224 * returns 0 on success.
4226 int CtdlDoIHavePermissionToPostInThisRoom(
4229 const char* RemoteIdentifier,
4235 if (!(CC->logged_in) &&
4236 (PostPublic == POST_LOGGED_IN)) {
4237 snprintf(errmsgbuf, n, "Not logged in.");
4238 return (ERROR + NOT_LOGGED_IN);
4240 else if (PostPublic == CHECK_EXISTANCE) {
4241 return (0); // We're Evaling whether a recipient exists
4243 else if (!(CC->logged_in)) {
4245 if ((CC->room.QRflags & QR_READONLY)) {
4246 snprintf(errmsgbuf, n, "Not logged in.");
4247 return (ERROR + NOT_LOGGED_IN);
4249 if (CC->room.QRflags2 & QR2_MODERATED) {
4250 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4251 return (ERROR + NOT_LOGGED_IN);
4253 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4255 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4261 if ((CC->user.axlevel < AxProbU)
4262 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4263 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4264 return (ERROR + HIGHER_ACCESS_REQUIRED);
4267 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4269 if (ra & UA_POSTALLOWED) {
4270 strcpy(errmsgbuf, "OK to post or reply here");
4274 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4276 * To be thorough, we ought to check to see if the message they are
4277 * replying to is actually a valid one in this room, but unless this
4278 * actually becomes a problem we'll go with high performance instead.
4280 strcpy(errmsgbuf, "OK to reply here");
4284 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4285 /* Clarify what happened with a better error message */
4286 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4287 return (ERROR + HIGHER_ACCESS_REQUIRED);
4290 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4291 return (ERROR + HIGHER_ACCESS_REQUIRED);
4297 * Check to see if the specified user has Internet mail permission
4298 * (returns nonzero if permission is granted)
4300 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4302 /* Do not allow twits to send Internet mail */
4303 if (who->axlevel <= AxProbU) return(0);
4305 /* Globally enabled? */
4306 if (config.c_restrict == 0) return(1);
4308 /* User flagged ok? */
4309 if (who->flags & US_INTERNET) return(2);
4311 /* Admin level access? */
4312 if (who->axlevel >= AxAideU) return(3);
4314 /* No mail for you! */
4320 * Validate recipients, count delivery types and errors, and handle aliasing
4321 * FIXME check for dupes!!!!!
4323 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4324 * were specified, or the number of addresses found invalid.
4326 * Caller needs to free the result using free_recipients()
4328 struct recptypes *validate_recipients(const char *supplied_recipients,
4329 const char *RemoteIdentifier,
4331 struct CitContext *CCC = CC;
4332 struct recptypes *ret;
4333 char *recipients = NULL;
4335 char this_recp[256];
4336 char this_recp_cooked[256];
4343 struct ctdluser tempUS;
4344 struct ctdlroom tempQR;
4345 struct ctdlroom tempQR2;
4351 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4352 if (ret == NULL) return(NULL);
4354 /* Set all strings to null and numeric values to zero */
4355 memset(ret, 0, sizeof(struct recptypes));
4357 if (supplied_recipients == NULL) {
4358 recipients = strdup("");
4361 recipients = strdup(supplied_recipients);
4364 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4365 * actually need, but it's healthier for the heap than doing lots of tiny
4366 * realloc() calls instead.
4368 len = strlen(recipients) + 1024;
4369 ret->errormsg = malloc(len);
4370 ret->recp_local = malloc(len);
4371 ret->recp_internet = malloc(len);
4372 ret->recp_ignet = malloc(len);
4373 ret->recp_room = malloc(len);
4374 ret->display_recp = malloc(len);
4375 ret->recp_orgroom = malloc(len);
4376 org_recp = malloc(len);
4378 ret->errormsg[0] = 0;
4379 ret->recp_local[0] = 0;
4380 ret->recp_internet[0] = 0;
4381 ret->recp_ignet[0] = 0;
4382 ret->recp_room[0] = 0;
4383 ret->recp_orgroom[0] = 0;
4384 ret->display_recp[0] = 0;
4386 ret->recptypes_magic = RECPTYPES_MAGIC;
4388 /* Change all valid separator characters to commas */
4389 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4390 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4391 recipients[i] = ',';
4395 /* Now start extracting recipients... */
4397 while (!IsEmptyStr(recipients)) {
4398 for (i=0; i<=strlen(recipients); ++i) {
4399 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4400 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4401 safestrncpy(this_recp, recipients, i+1);
4403 if (recipients[i] == ',') {
4404 strcpy(recipients, &recipients[i+1]);
4407 strcpy(recipients, "");
4414 if (IsEmptyStr(this_recp))
4416 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4419 strcpy(org_recp, this_recp);
4422 mailtype = alias(this_recp);
4424 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4425 if (this_recp[j]=='_') {
4426 this_recp_cooked[j] = ' ';
4429 this_recp_cooked[j] = this_recp[j];
4432 this_recp_cooked[j] = '\0';
4437 if (!strcasecmp(this_recp, "sysop")) {
4439 strcpy(this_recp, config.c_aideroom);
4440 if (!IsEmptyStr(ret->recp_room)) {
4441 strcat(ret->recp_room, "|");
4443 strcat(ret->recp_room, this_recp);
4445 else if ( (!strncasecmp(this_recp, "room_", 5))
4446 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4448 /* Save room so we can restore it later */
4449 tempQR2 = CCC->room;
4452 /* Check permissions to send mail to this room */
4453 err = CtdlDoIHavePermissionToPostInThisRoom(
4458 0 /* 0 = not a reply */
4467 if (!IsEmptyStr(ret->recp_room)) {
4468 strcat(ret->recp_room, "|");
4470 strcat(ret->recp_room, &this_recp_cooked[5]);
4472 if (!IsEmptyStr(ret->recp_orgroom)) {
4473 strcat(ret->recp_orgroom, "|");
4475 strcat(ret->recp_orgroom, org_recp);
4479 /* Restore room in case something needs it */
4480 CCC->room = tempQR2;
4483 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4485 strcpy(this_recp, tempUS.fullname);
4486 if (!IsEmptyStr(ret->recp_local)) {
4487 strcat(ret->recp_local, "|");
4489 strcat(ret->recp_local, this_recp);
4491 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4493 strcpy(this_recp, tempUS.fullname);
4494 if (!IsEmptyStr(ret->recp_local)) {
4495 strcat(ret->recp_local, "|");
4497 strcat(ret->recp_local, this_recp);
4505 /* Yes, you're reading this correctly: if the target
4506 * domain points back to the local system or an attached
4507 * Citadel directory, the address is invalid. That's
4508 * because if the address were valid, we would have
4509 * already translated it to a local address by now.
4511 if (IsDirectory(this_recp, 0)) {
4516 ++ret->num_internet;
4517 if (!IsEmptyStr(ret->recp_internet)) {
4518 strcat(ret->recp_internet, "|");
4520 strcat(ret->recp_internet, this_recp);
4525 if (!IsEmptyStr(ret->recp_ignet)) {
4526 strcat(ret->recp_ignet, "|");
4528 strcat(ret->recp_ignet, this_recp);
4536 if (IsEmptyStr(errmsg)) {
4537 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4540 snprintf(append, sizeof append, "%s", errmsg);
4542 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4543 if (!IsEmptyStr(ret->errormsg)) {
4544 strcat(ret->errormsg, "; ");
4546 strcat(ret->errormsg, append);
4550 if (IsEmptyStr(ret->display_recp)) {
4551 strcpy(append, this_recp);
4554 snprintf(append, sizeof append, ", %s", this_recp);
4556 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4557 strcat(ret->display_recp, append);
4563 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4564 ret->num_room + ret->num_error) == 0) {
4565 ret->num_error = (-1);
4566 strcpy(ret->errormsg, "No recipients specified.");
4569 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4570 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4571 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4572 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4573 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4574 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4582 * Destructor for struct recptypes
4584 void free_recipients(struct recptypes *valid) {
4586 if (valid == NULL) {
4590 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4591 struct CitContext *CCC = CC;
4592 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4596 if (valid->errormsg != NULL) free(valid->errormsg);
4597 if (valid->recp_local != NULL) free(valid->recp_local);
4598 if (valid->recp_internet != NULL) free(valid->recp_internet);
4599 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4600 if (valid->recp_room != NULL) free(valid->recp_room);
4601 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4602 if (valid->display_recp != NULL) free(valid->display_recp);
4603 if (valid->bounce_to != NULL) free(valid->bounce_to);
4604 if (valid->envelope_from != NULL) free(valid->envelope_from);
4605 if (valid->sending_room != NULL) free(valid->sending_room);
4612 * message entry - mode 0 (normal)
4614 void cmd_ent0(char *entargs)
4616 struct CitContext *CCC = CC;
4621 char supplied_euid[128];
4623 int format_type = 0;
4624 char newusername[256];
4625 char newuseremail[256];
4626 struct CtdlMessage *msg;
4630 struct recptypes *valid = NULL;
4631 struct recptypes *valid_to = NULL;
4632 struct recptypes *valid_cc = NULL;
4633 struct recptypes *valid_bcc = NULL;
4635 int subject_required = 0;
4640 int newuseremail_ok = 0;
4641 char references[SIZ];
4646 post = extract_int(entargs, 0);
4647 extract_token(recp, entargs, 1, '|', sizeof recp);
4648 anon_flag = extract_int(entargs, 2);
4649 format_type = extract_int(entargs, 3);
4650 extract_token(subject, entargs, 4, '|', sizeof subject);
4651 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4652 do_confirm = extract_int(entargs, 6);
4653 extract_token(cc, entargs, 7, '|', sizeof cc);
4654 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4655 switch(CC->room.QRdefaultview) {
4658 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4661 supplied_euid[0] = 0;
4664 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4665 extract_token(references, entargs, 11, '|', sizeof references);
4666 for (ptr=references; *ptr != 0; ++ptr) {
4667 if (*ptr == '!') *ptr = '|';
4670 /* first check to make sure the request is valid. */
4672 err = CtdlDoIHavePermissionToPostInThisRoom(
4677 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4681 cprintf("%d %s\n", err, errmsg);
4685 /* Check some other permission type things. */
4687 if (IsEmptyStr(newusername)) {
4688 strcpy(newusername, CCC->user.fullname);
4690 if ( (CCC->user.axlevel < AxAideU)
4691 && (strcasecmp(newusername, CCC->user.fullname))
4692 && (strcasecmp(newusername, CCC->cs_inet_fn))
4694 cprintf("%d You don't have permission to author messages as '%s'.\n",
4695 ERROR + HIGHER_ACCESS_REQUIRED,
4702 if (IsEmptyStr(newuseremail)) {
4703 newuseremail_ok = 1;
4706 if (!IsEmptyStr(newuseremail)) {
4707 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4708 newuseremail_ok = 1;
4710 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4711 j = num_tokens(CCC->cs_inet_other_emails, '|');
4712 for (i=0; i<j; ++i) {
4713 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4714 if (!strcasecmp(newuseremail, buf)) {
4715 newuseremail_ok = 1;
4721 if (!newuseremail_ok) {
4722 cprintf("%d You don't have permission to author messages as '%s'.\n",
4723 ERROR + HIGHER_ACCESS_REQUIRED,
4729 CCC->cs_flags |= CS_POSTING;
4731 /* In mailbox rooms we have to behave a little differently --
4732 * make sure the user has specified at least one recipient. Then
4733 * validate the recipient(s). We do this for the Mail> room, as
4734 * well as any room which has the "Mailbox" view set - unless it
4735 * is the DRAFTS room which does not require recipients
4738 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4739 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4740 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4741 if (CCC->user.axlevel < AxProbU) {
4742 strcpy(recp, "sysop");
4747 valid_to = validate_recipients(recp, NULL, 0);
4748 if (valid_to->num_error > 0) {
4749 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4750 free_recipients(valid_to);
4754 valid_cc = validate_recipients(cc, NULL, 0);
4755 if (valid_cc->num_error > 0) {
4756 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4757 free_recipients(valid_to);
4758 free_recipients(valid_cc);
4762 valid_bcc = validate_recipients(bcc, NULL, 0);
4763 if (valid_bcc->num_error > 0) {
4764 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4765 free_recipients(valid_to);
4766 free_recipients(valid_cc);
4767 free_recipients(valid_bcc);
4771 /* Recipient required, but none were specified */
4772 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4773 free_recipients(valid_to);
4774 free_recipients(valid_cc);
4775 free_recipients(valid_bcc);
4776 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4780 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4781 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4782 cprintf("%d You do not have permission "
4783 "to send Internet mail.\n",
4784 ERROR + HIGHER_ACCESS_REQUIRED);
4785 free_recipients(valid_to);
4786 free_recipients(valid_cc);
4787 free_recipients(valid_bcc);
4792 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)
4793 && (CCC->user.axlevel < AxNetU) ) {
4794 cprintf("%d Higher access required for network mail.\n",
4795 ERROR + HIGHER_ACCESS_REQUIRED);
4796 free_recipients(valid_to);
4797 free_recipients(valid_cc);
4798 free_recipients(valid_bcc);
4802 if ((RESTRICT_INTERNET == 1)
4803 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4804 && ((CCC->user.flags & US_INTERNET) == 0)
4805 && (!CCC->internal_pgm)) {
4806 cprintf("%d You don't have access to Internet mail.\n",
4807 ERROR + HIGHER_ACCESS_REQUIRED);
4808 free_recipients(valid_to);
4809 free_recipients(valid_cc);
4810 free_recipients(valid_bcc);
4816 /* Is this a room which has anonymous-only or anonymous-option? */
4817 anonymous = MES_NORMAL;
4818 if (CCC->room.QRflags & QR_ANONONLY) {
4819 anonymous = MES_ANONONLY;
4821 if (CCC->room.QRflags & QR_ANONOPT) {
4822 if (anon_flag == 1) { /* only if the user requested it */
4823 anonymous = MES_ANONOPT;
4827 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4831 /* Recommend to the client that the use of a message subject is
4832 * strongly recommended in this room, if either the SUBJECTREQ flag
4833 * is set, or if there is one or more Internet email recipients.
4835 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4836 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4837 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4838 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4840 /* If we're only checking the validity of the request, return
4841 * success without creating the message.
4844 cprintf("%d %s|%d\n", CIT_OK,
4845 ((valid_to != NULL) ? valid_to->display_recp : ""),
4847 free_recipients(valid_to);
4848 free_recipients(valid_cc);
4849 free_recipients(valid_bcc);
4853 /* We don't need these anymore because we'll do it differently below */
4854 free_recipients(valid_to);
4855 free_recipients(valid_cc);
4856 free_recipients(valid_bcc);
4858 /* Read in the message from the client. */
4860 cprintf("%d send message\n", START_CHAT_MODE);
4862 cprintf("%d send message\n", SEND_LISTING);
4865 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4866 CCC->room.QRname, anonymous, format_type,
4867 newusername, newuseremail, subject,
4868 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4871 /* Put together one big recipients struct containing to/cc/bcc all in
4872 * one. This is for the envelope.
4874 char *all_recps = malloc(SIZ * 3);
4875 strcpy(all_recps, recp);
4876 if (!IsEmptyStr(cc)) {
4877 if (!IsEmptyStr(all_recps)) {
4878 strcat(all_recps, ",");
4880 strcat(all_recps, cc);
4882 if (!IsEmptyStr(bcc)) {
4883 if (!IsEmptyStr(all_recps)) {
4884 strcat(all_recps, ",");
4886 strcat(all_recps, bcc);
4888 if (!IsEmptyStr(all_recps)) {
4889 valid = validate_recipients(all_recps, NULL, 0);
4896 if ((valid != NULL) && (valid->num_room == 1))
4898 /* posting into an ML room? set the envelope from
4899 * to the actual mail address so others get a valid
4902 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4906 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4908 cprintf("%ld\n", msgnum);
4910 if (StrLength(CCC->StatusMessage) > 0) {
4911 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4913 else if (msgnum >= 0L) {
4914 client_write(HKEY("Message accepted.\n"));
4917 client_write(HKEY("Internal error.\n"));
4920 if (!CM_IsEmpty(msg, eExclusiveID)) {
4921 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4928 CtdlFreeMessage(msg);
4930 if (valid != NULL) {
4931 free_recipients(valid);
4939 * API function to delete messages which match a set of criteria
4940 * (returns the actual number of messages deleted)
4942 int CtdlDeleteMessages(char *room_name, /* which room */
4943 long *dmsgnums, /* array of msg numbers to be deleted */
4944 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4945 char *content_type /* or "" for any. regular expressions expected. */
4948 struct CitContext *CCC = CC;
4949 struct ctdlroom qrbuf;
4950 struct cdbdata *cdbfr;
4951 long *msglist = NULL;
4952 long *dellist = NULL;
4955 int num_deleted = 0;
4957 struct MetaData smi;
4960 int need_to_free_re = 0;
4962 if (content_type) if (!IsEmptyStr(content_type)) {
4963 regcomp(&re, content_type, 0);
4964 need_to_free_re = 1;
4966 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4967 room_name, num_dmsgnums, content_type);
4969 /* get room record, obtaining a lock... */
4970 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4971 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4973 if (need_to_free_re) regfree(&re);
4974 return (0); /* room not found */
4976 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4978 if (cdbfr != NULL) {
4979 dellist = malloc(cdbfr->len);
4980 msglist = (long *) cdbfr->ptr;
4981 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4982 num_msgs = cdbfr->len / sizeof(long);
4986 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4987 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4988 int have_more_del = 1;
4990 num_msgs = sort_msglist(msglist, num_msgs);
4991 if (num_dmsgnums > 1)
4992 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4995 StrBuf *dbg = NewStrBuf();
4996 for (i = 0; i < num_dmsgnums; i++)
4997 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4998 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
5003 while ((i < num_msgs) && (have_more_del)) {
5006 /* Set/clear a bit for each criterion */
5008 /* 0 messages in the list or a null list means that we are
5009 * interested in deleting any messages which meet the other criteria.
5012 delete_this |= 0x01;
5015 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
5020 if (msglist[i] == dmsgnums[j]) {
5021 delete_this |= 0x01;
5024 have_more_del = (j < num_dmsgnums);
5027 if (have_contenttype) {
5028 GetMetaData(&smi, msglist[i]);
5029 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
5030 delete_this |= 0x02;
5033 delete_this |= 0x02;
5036 /* Delete message only if all bits are set */
5037 if (delete_this == 0x03) {
5038 dellist[num_deleted++] = msglist[i];
5045 StrBuf *dbg = NewStrBuf();
5046 for (i = 0; i < num_deleted; i++)
5047 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
5048 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
5052 num_msgs = sort_msglist(msglist, num_msgs);
5053 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
5054 msglist, (int)(num_msgs * sizeof(long)));
5057 qrbuf.QRhighest = msglist[num_msgs - 1];
5059 qrbuf.QRhighest = 0;
5061 CtdlPutRoomLock(&qrbuf);
5063 /* Go through the messages we pulled out of the index, and decrement
5064 * their reference counts by 1. If this is the only room the message
5065 * was in, the reference count will reach zero and the message will
5066 * automatically be deleted from the database. We do this in a
5067 * separate pass because there might be plug-in hooks getting called,
5068 * and we don't want that happening during an S_ROOMS critical
5072 for (i=0; i<num_deleted; ++i) {
5073 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
5075 AdjRefCountList(dellist, num_deleted, -1);
5077 /* Now free the memory we used, and go away. */
5078 if (msglist != NULL) free(msglist);
5079 if (dellist != NULL) free(dellist);
5080 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
5081 if (need_to_free_re) regfree(&re);
5082 return (num_deleted);
5088 * Check whether the current user has permission to delete messages from
5089 * the current room (returns 1 for yes, 0 for no)
5091 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
5093 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
5094 if (ra & UA_DELETEALLOWED) return(1);
5102 * Delete message from current room
5104 void cmd_dele(char *args)
5113 extract_token(msgset, args, 0, '|', sizeof msgset);
5114 num_msgs = num_tokens(msgset, ',');
5116 cprintf("%d Nothing to do.\n", CIT_OK);
5120 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
5121 cprintf("%d Higher access required.\n",
5122 ERROR + HIGHER_ACCESS_REQUIRED);
5127 * Build our message set to be moved/copied
5129 msgs = malloc(num_msgs * sizeof(long));
5130 for (i=0; i<num_msgs; ++i) {
5131 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5132 msgs[i] = atol(msgtok);
5135 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5139 cprintf("%d %d message%s deleted.\n", CIT_OK,
5140 num_deleted, ((num_deleted != 1) ? "s" : ""));
5142 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
5150 * move or copy a message to another room
5152 void cmd_move(char *args)
5159 char targ[ROOMNAMELEN];
5160 struct ctdlroom qtemp;
5167 extract_token(msgset, args, 0, '|', sizeof msgset);
5168 num_msgs = num_tokens(msgset, ',');
5170 cprintf("%d Nothing to do.\n", CIT_OK);
5174 extract_token(targ, args, 1, '|', sizeof targ);
5175 convert_room_name_macros(targ, sizeof targ);
5176 targ[ROOMNAMELEN - 1] = 0;
5177 is_copy = extract_int(args, 2);
5179 if (CtdlGetRoom(&qtemp, targ) != 0) {
5180 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5184 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5185 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5189 CtdlGetUser(&CC->user, CC->curr_user);
5190 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5192 /* Check for permission to perform this operation.
5193 * Remember: "CC->room" is source, "qtemp" is target.
5197 /* Admins can move/copy */
5198 if (CC->user.axlevel >= AxAideU) permit = 1;
5200 /* Room aides can move/copy */
5201 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5203 /* Permit move/copy from personal rooms */
5204 if ((CC->room.QRflags & QR_MAILBOX)
5205 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5207 /* Permit only copy from public to personal room */
5209 && (!(CC->room.QRflags & QR_MAILBOX))
5210 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5212 /* Permit message removal from collaborative delete rooms */
5213 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5215 /* Users allowed to post into the target room may move into it too. */
5216 if ((CC->room.QRflags & QR_MAILBOX) &&
5217 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5219 /* User must have access to target room */
5220 if (!(ra & UA_KNOWN)) permit = 0;
5223 cprintf("%d Higher access required.\n",
5224 ERROR + HIGHER_ACCESS_REQUIRED);
5229 * Build our message set to be moved/copied
5231 msgs = malloc(num_msgs * sizeof(long));
5232 for (i=0; i<num_msgs; ++i) {
5233 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5234 msgs[i] = atol(msgtok);
5240 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5242 cprintf("%d Cannot store message(s) in %s: error %d\n",
5248 /* Now delete the message from the source room,
5249 * if this is a 'move' rather than a 'copy' operation.
5252 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5256 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5262 * GetMetaData() - Get the supplementary record for a message
5264 void GetMetaData(struct MetaData *smibuf, long msgnum)
5267 struct cdbdata *cdbsmi;
5270 memset(smibuf, 0, sizeof(struct MetaData));
5271 smibuf->meta_msgnum = msgnum;
5272 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5274 /* Use the negative of the message number for its supp record index */
5275 TheIndex = (0L - msgnum);
5277 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5278 if (cdbsmi == NULL) {
5279 return; /* record not found; go with defaults */
5281 memcpy(smibuf, cdbsmi->ptr,
5282 ((cdbsmi->len > sizeof(struct MetaData)) ?
5283 sizeof(struct MetaData) : cdbsmi->len));
5290 * PutMetaData() - (re)write supplementary record for a message
5292 void PutMetaData(struct MetaData *smibuf)
5296 /* Use the negative of the message number for the metadata db index */
5297 TheIndex = (0L - smibuf->meta_msgnum);
5299 cdb_store(CDB_MSGMAIN,
5300 &TheIndex, (int)sizeof(long),
5301 smibuf, (int)sizeof(struct MetaData));
5306 * AdjRefCount - submit an adjustment to the reference count for a message.
5307 * (These are just queued -- we actually process them later.)
5309 void AdjRefCount(long msgnum, int incr)
5311 struct CitContext *CCC = CC;
5312 struct arcq new_arcq;
5315 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5317 begin_critical_section(S_SUPPMSGMAIN);
5318 if (arcfp == NULL) {
5319 arcfp = fopen(file_arcq, "ab+");
5320 chown(file_arcq, CTDLUID, (-1));
5321 chmod(file_arcq, 0600);
5323 end_critical_section(S_SUPPMSGMAIN);
5325 /* msgnum < 0 means that we're trying to close the file */
5327 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5328 begin_critical_section(S_SUPPMSGMAIN);
5329 if (arcfp != NULL) {
5333 end_critical_section(S_SUPPMSGMAIN);
5338 * If we can't open the queue, perform the operation synchronously.
5340 if (arcfp == NULL) {
5341 TDAP_AdjRefCount(msgnum, incr);
5345 new_arcq.arcq_msgnum = msgnum;
5346 new_arcq.arcq_delta = incr;
5347 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5349 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5358 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5360 struct CitContext *CCC = CC;
5361 long i, the_size, offset;
5362 struct arcq *new_arcq;
5365 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5367 begin_critical_section(S_SUPPMSGMAIN);
5368 if (arcfp == NULL) {
5369 arcfp = fopen(file_arcq, "ab+");
5370 chown(file_arcq, CTDLUID, (-1));
5371 chmod(file_arcq, 0600);
5373 end_critical_section(S_SUPPMSGMAIN);
5376 * If we can't open the queue, perform the operation synchronously.
5378 if (arcfp == NULL) {
5379 for (i = 0; i < nmsg; i++)
5380 TDAP_AdjRefCount(msgnum[i], incr);
5384 the_size = sizeof(struct arcq) * nmsg;
5385 new_arcq = malloc(the_size);
5386 for (i = 0; i < nmsg; i++) {
5387 new_arcq[i].arcq_msgnum = msgnum[i];
5388 new_arcq[i].arcq_delta = incr;
5392 while ((rv >= 0) && (offset < the_size))
5394 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5396 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5412 * TDAP_ProcessAdjRefCountQueue()
5414 * Process the queue of message count adjustments that was created by calls
5415 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5416 * for each one. This should be an "off hours" operation.
5418 int TDAP_ProcessAdjRefCountQueue(void)
5420 struct CitContext *CCC = CC;
5421 char file_arcq_temp[PATH_MAX];
5424 struct arcq arcq_rec;
5425 int num_records_processed = 0;
5427 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5429 begin_critical_section(S_SUPPMSGMAIN);
5430 if (arcfp != NULL) {
5435 r = link(file_arcq, file_arcq_temp);
5437 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5438 end_critical_section(S_SUPPMSGMAIN);
5439 return(num_records_processed);
5443 end_critical_section(S_SUPPMSGMAIN);
5445 fp = fopen(file_arcq_temp, "rb");
5447 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5448 return(num_records_processed);
5451 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5452 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5453 ++num_records_processed;
5457 r = unlink(file_arcq_temp);
5459 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5462 return(num_records_processed);
5468 * TDAP_AdjRefCount - adjust the reference count for a message.
5469 * This one does it "for real" because it's called by
5470 * the autopurger function that processes the queue
5471 * created by AdjRefCount(). If a message's reference
5472 * count becomes zero, we also delete the message from
5473 * disk and de-index it.
5475 void TDAP_AdjRefCount(long msgnum, int incr)
5477 struct CitContext *CCC = CC;
5479 struct MetaData smi;
5482 /* This is a *tight* critical section; please keep it that way, as
5483 * it may get called while nested in other critical sections.
5484 * Complicating this any further will surely cause deadlock!
5486 begin_critical_section(S_SUPPMSGMAIN);
5487 GetMetaData(&smi, msgnum);
5488 smi.meta_refcount += incr;
5490 end_critical_section(S_SUPPMSGMAIN);
5491 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5492 msgnum, incr, smi.meta_refcount
5495 /* If the reference count is now zero, delete the message
5496 * (and its supplementary record as well).
5498 if (smi.meta_refcount == 0) {
5499 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5501 /* Call delete hooks with NULL room to show it has gone altogether */
5502 PerformDeleteHooks(NULL, msgnum);
5504 /* Remove from message base */
5506 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5507 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5509 /* Remove metadata record */
5510 delnum = (0L - msgnum);
5511 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5517 * Write a generic object to this room
5519 * Note: this could be much more efficient. Right now we use two temporary
5520 * files, and still pull the message into memory as with all others.
5522 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5523 char *content_type, /* MIME type of this object */
5524 char *raw_message, /* Data to be written */
5525 off_t raw_length, /* Size of raw_message */
5526 struct ctdluser *is_mailbox, /* Mailbox room? */
5527 int is_binary, /* Is encoding necessary? */
5528 int is_unique, /* Del others of this type? */
5529 unsigned int flags /* Internal save flags */
5532 struct CitContext *CCC = CC;
5533 struct ctdlroom qrbuf;
5534 char roomname[ROOMNAMELEN];
5535 struct CtdlMessage *msg;
5536 char *encoded_message = NULL;
5538 if (is_mailbox != NULL) {
5539 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5542 safestrncpy(roomname, req_room, sizeof(roomname));
5545 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5548 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5551 encoded_message = malloc((size_t)(raw_length + 4096));
5554 sprintf(encoded_message, "Content-type: %s\n", content_type);
5557 sprintf(&encoded_message[strlen(encoded_message)],
5558 "Content-transfer-encoding: base64\n\n"
5562 sprintf(&encoded_message[strlen(encoded_message)],
5563 "Content-transfer-encoding: 7bit\n\n"
5569 &encoded_message[strlen(encoded_message)],
5577 &encoded_message[strlen(encoded_message)],
5583 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5584 msg = malloc(sizeof(struct CtdlMessage));
5585 memset(msg, 0, sizeof(struct CtdlMessage));
5586 msg->cm_magic = CTDLMESSAGE_MAGIC;
5587 msg->cm_anon_type = MES_NORMAL;
5588 msg->cm_format_type = 4;
5589 msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
5590 msg->cm_fields[eOriginalRoom] = strdup(req_room);
5591 msg->cm_fields[eNodeName] = strdup(config.c_nodename);
5592 msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
5593 msg->cm_flags = flags;
5595 msg->cm_fields[eMesageText] = encoded_message;
5597 /* Create the requested room if we have to. */
5598 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5599 CtdlCreateRoom(roomname,
5600 ( (is_mailbox != NULL) ? 5 : 3 ),
5601 "", 0, 1, 0, VIEW_BBS);
5603 /* If the caller specified this object as unique, delete all
5604 * other objects of this type that are currently in the room.
5607 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5608 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5611 /* Now write the data */
5612 CtdlSubmitMsg(msg, NULL, roomname, 0);
5613 CtdlFreeMessage(msg);
5621 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5622 config_msgnum = msgnum;
5626 char *CtdlGetSysConfig(char *sysconfname) {
5627 char hold_rm[ROOMNAMELEN];
5630 struct CtdlMessage *msg;
5633 strcpy(hold_rm, CC->room.QRname);
5634 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5635 CtdlGetRoom(&CC->room, hold_rm);
5640 /* We want the last (and probably only) config in this room */
5641 begin_critical_section(S_CONFIG);
5642 config_msgnum = (-1L);
5643 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5644 CtdlGetSysConfigBackend, NULL);
5645 msgnum = config_msgnum;
5646 end_critical_section(S_CONFIG);
5652 msg = CtdlFetchMessage(msgnum, 1);
5654 conf = strdup(msg->cm_fields[eMesageText]);
5655 CtdlFreeMessage(msg);
5662 CtdlGetRoom(&CC->room, hold_rm);
5664 if (conf != NULL) do {
5665 extract_token(buf, conf, 0, '\n', sizeof buf);
5666 strcpy(conf, &conf[strlen(buf)+1]);
5667 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5673 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5674 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5679 * Determine whether a given Internet address belongs to the current user
5681 int CtdlIsMe(char *addr, int addr_buf_len)
5683 struct recptypes *recp;
5686 recp = validate_recipients(addr, NULL, 0);
5687 if (recp == NULL) return(0);
5689 if (recp->num_local == 0) {
5690 free_recipients(recp);
5694 for (i=0; i<recp->num_local; ++i) {
5695 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5696 if (!strcasecmp(addr, CC->user.fullname)) {
5697 free_recipients(recp);
5702 free_recipients(recp);
5708 * Citadel protocol command to do the same
5710 void cmd_isme(char *argbuf) {
5713 if (CtdlAccessCheck(ac_logged_in)) return;
5714 extract_token(addr, argbuf, 0, '|', sizeof addr);
5716 if (CtdlIsMe(addr, sizeof addr)) {
5717 cprintf("%d %s\n", CIT_OK, addr);
5720 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5726 /*****************************************************************************/
5727 /* MODULE INITIALIZATION STUFF */
5728 /*****************************************************************************/
5729 void SetMessageDebugEnabled(const int n)
5731 MessageDebugEnabled = n;
5733 CTDL_MODULE_INIT(msgbase)
5736 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5738 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5739 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5740 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5741 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5742 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5743 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5744 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5745 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5746 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5747 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5748 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5749 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5752 /* return our Subversion id for the Log */