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);
159 void CtdlMsgSetCM_Fields(struct CtdlMessage *Msg, eMsgField which, const char *buf, long length)
161 if (Msg->cm_fields[which] != NULL)
162 free (Msg->cm_fields[which]);
163 Msg->cm_fields[which] = malloc(length + 1);
164 memcpy(Msg->cm_fields[which], buf, length);
165 Msg->cm_fields[which][length] = '\0';
169 * This function is self explanatory.
170 * (What can I say, I'm in a weird mood today...)
172 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
176 for (i = 0; i < strlen(name); ++i) {
177 if (name[i] == '@') {
178 while (isspace(name[i - 1]) && i > 0) {
179 strcpy(&name[i - 1], &name[i]);
182 while (isspace(name[i + 1])) {
183 strcpy(&name[i + 1], &name[i + 2]);
191 * Aliasing for network mail.
192 * (Error messages have been commented out, because this is a server.)
194 int alias(char *name)
195 { /* process alias and routing info for mail */
196 struct CitContext *CCC = CC;
199 char aaa[SIZ], bbb[SIZ];
200 char *ignetcfg = NULL;
201 char *ignetmap = NULL;
207 char original_name[256];
208 safestrncpy(original_name, name, sizeof original_name);
211 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
212 stripallbut(name, '<', '>');
214 fp = fopen(file_mail_aliases, "r");
216 fp = fopen("/dev/null", "r");
223 while (fgets(aaa, sizeof aaa, fp) != NULL) {
224 while (isspace(name[0]))
225 strcpy(name, &name[1]);
226 aaa[strlen(aaa) - 1] = 0;
228 for (a = 0; a < strlen(aaa); ++a) {
230 strcpy(bbb, &aaa[a + 1]);
234 if (!strcasecmp(name, aaa))
239 /* Hit the Global Address Book */
240 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
244 if (strcasecmp(original_name, name)) {
245 MSG_syslog(LOG_INFO, "%s is being forwarded to %s\n", original_name, name);
248 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
249 for (a=0; a<strlen(name); ++a) {
250 if (name[a] == '@') {
251 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
253 MSG_syslog(LOG_INFO, "Changed to <%s>\n", name);
258 /* determine local or remote type, see citadel.h */
259 at = haschar(name, '@');
260 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
261 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
262 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
264 /* figure out the delivery mode */
265 extract_token(node, name, 1, '@', sizeof node);
267 /* If there are one or more dots in the nodename, we assume that it
268 * is an FQDN and will attempt SMTP delivery to the Internet.
270 if (haschar(node, '.') > 0) {
271 return(MES_INTERNET);
274 /* Otherwise we look in the IGnet maps for a valid Citadel node.
275 * Try directly-connected nodes first...
277 ignetcfg = CtdlGetSysConfig(IGNETCFG);
278 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
279 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
280 extract_token(testnode, buf, 0, '|', sizeof testnode);
281 if (!strcasecmp(node, testnode)) {
289 * Then try nodes that are two or more hops away.
291 ignetmap = CtdlGetSysConfig(IGNETMAP);
292 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
293 extract_token(buf, ignetmap, i, '\n', sizeof buf);
294 extract_token(testnode, buf, 0, '|', sizeof testnode);
295 if (!strcasecmp(node, testnode)) {
302 /* If we get to this point it's an invalid node name */
308 * Back end for the MSGS command: output message number only.
310 void simple_listing(long msgnum, void *userdata)
312 cprintf("%ld\n", msgnum);
318 * Back end for the MSGS command: output header summary.
320 void headers_listing(long msgnum, void *userdata)
322 struct CtdlMessage *msg;
324 msg = CtdlFetchMessage(msgnum, 0);
326 cprintf("%ld|0|||||\n", msgnum);
330 cprintf("%ld|%s|%s|%s|%s|%s|\n",
332 (msg->cm_fields[eTimestamp] ? msg->cm_fields[eTimestamp] : "0"),
333 (msg->cm_fields[eAuthor] ? msg->cm_fields[eAuthor] : ""),
334 (msg->cm_fields[eNodeName] ? msg->cm_fields[eNodeName] : ""),
335 (msg->cm_fields[erFc822Addr] ? msg->cm_fields[erFc822Addr] : ""),
336 (msg->cm_fields[eMsgSubject] ? msg->cm_fields[eMsgSubject] : "")
338 CtdlFreeMessage(msg);
342 * Back end for the MSGS command: output EUID header.
344 void headers_euid(long msgnum, void *userdata)
346 struct CtdlMessage *msg;
348 msg = CtdlFetchMessage(msgnum, 0);
350 cprintf("%ld||\n", msgnum);
354 cprintf("%ld|%s|%s\n",
356 (msg->cm_fields[eExclusiveID] ? msg->cm_fields[eExclusiveID] : ""),
357 (msg->cm_fields[eTimestamp] ? msg->cm_fields[eTimestamp] : "0"));
358 CtdlFreeMessage(msg);
365 /* Determine if a given message matches the fields in a message template.
366 * Return 0 for a successful match.
368 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
371 /* If there aren't any fields in the template, all messages will
374 if (template == NULL) return(0);
376 /* Null messages are bogus. */
377 if (msg == NULL) return(1);
379 for (i='A'; i<='Z'; ++i) {
380 if (template->cm_fields[i] != NULL) {
381 if (msg->cm_fields[i] == NULL) {
382 /* Considered equal if temmplate is empty string */
383 if (IsEmptyStr(template->cm_fields[i])) continue;
386 if (strcasecmp(msg->cm_fields[i],
387 template->cm_fields[i])) return 1;
391 /* All compares succeeded: we have a match! */
398 * Retrieve the "seen" message list for the current room.
400 void CtdlGetSeen(char *buf, int which_set) {
401 struct CitContext *CCC = CC;
404 /* Learn about the user and room in question */
405 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
407 if (which_set == ctdlsetseen_seen)
408 safestrncpy(buf, vbuf.v_seen, SIZ);
409 if (which_set == ctdlsetseen_answered)
410 safestrncpy(buf, vbuf.v_answered, SIZ);
416 * Manipulate the "seen msgs" string (or other message set strings)
418 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
419 int target_setting, int which_set,
420 struct ctdluser *which_user, struct ctdlroom *which_room) {
421 struct CitContext *CCC = CC;
422 struct cdbdata *cdbfr;
427 long hi = (-1L); /// TODO: we just write here. y?
436 char *is_set; /* actually an array of booleans */
438 /* Don't bother doing *anything* if we were passed a list of zero messages */
439 if (num_target_msgnums < 1) {
443 /* If no room was specified, we go with the current room. */
445 which_room = &CCC->room;
448 /* If no user was specified, we go with the current user. */
450 which_user = &CCC->user;
453 MSG_syslog(LOG_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
454 num_target_msgnums, target_msgnums[0],
455 (target_setting ? "SET" : "CLEAR"),
459 /* Learn about the user and room in question */
460 CtdlGetRelationship(&vbuf, which_user, which_room);
462 /* Load the message list */
463 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
465 msglist = (long *) cdbfr->ptr;
466 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
467 num_msgs = cdbfr->len / sizeof(long);
470 return; /* No messages at all? No further action. */
473 is_set = malloc(num_msgs * sizeof(char));
474 memset(is_set, 0, (num_msgs * sizeof(char)) );
476 /* Decide which message set we're manipulating */
478 case ctdlsetseen_seen:
479 vset = NewStrBufPlain(vbuf.v_seen, -1);
481 case ctdlsetseen_answered:
482 vset = NewStrBufPlain(vbuf.v_answered, -1);
489 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
490 MSG_syslog(LOG_DEBUG, "There are %d messages in the room.\n", num_msgs);
491 for (i=0; i<num_msgs; ++i) {
492 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
494 MSG_syslog(LOG_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
495 for (k=0; k<num_target_msgnums; ++k) {
496 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
500 MSG_syslog(LOG_DEBUG, "before update: %s\n", ChrPtr(vset));
502 /* Translate the existing sequence set into an array of booleans */
503 setstr = NewStrBuf();
507 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
509 StrBufExtract_token(lostr, setstr, 0, ':');
510 if (StrBufNum_tokens(setstr, ':') >= 2) {
511 StrBufExtract_token(histr, setstr, 1, ':');
515 StrBufAppendBuf(histr, lostr, 0);
518 if (!strcmp(ChrPtr(histr), "*")) {
525 for (i = 0; i < num_msgs; ++i) {
526 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
536 /* Now translate the array of booleans back into a sequence set */
542 for (i=0; i<num_msgs; ++i) {
546 for (k=0; k<num_target_msgnums; ++k) {
547 if (msglist[i] == target_msgnums[k]) {
548 is_seen = target_setting;
552 if ((was_seen == 0) && (is_seen == 1)) {
555 else if ((was_seen == 1) && (is_seen == 0)) {
558 if (StrLength(vset) > 0) {
559 StrBufAppendBufPlain(vset, HKEY(","), 0);
562 StrBufAppendPrintf(vset, "%ld", hi);
565 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
569 if ((is_seen) && (i == num_msgs - 1)) {
570 if (StrLength(vset) > 0) {
571 StrBufAppendBufPlain(vset, HKEY(","), 0);
573 if ((i==0) || (was_seen == 0)) {
574 StrBufAppendPrintf(vset, "%ld", msglist[i]);
577 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
585 * We will have to stuff this string back into a 4096 byte buffer, so if it's
586 * larger than that now, truncate it by removing tokens from the beginning.
587 * The limit of 100 iterations is there to prevent an infinite loop in case
588 * something unexpected happens.
590 int number_of_truncations = 0;
591 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
592 StrBufRemove_token(vset, 0, ',');
593 ++number_of_truncations;
597 * If we're truncating the sequence set of messages marked with the 'seen' flag,
598 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
599 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
601 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
603 first_tok = NewStrBuf();
604 StrBufExtract_token(first_tok, vset, 0, ',');
605 StrBufRemove_token(vset, 0, ',');
607 if (StrBufNum_tokens(first_tok, ':') > 1) {
608 StrBufRemove_token(first_tok, 0, ':');
612 new_set = NewStrBuf();
613 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
614 StrBufAppendBuf(new_set, first_tok, 0);
615 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
616 StrBufAppendBuf(new_set, vset, 0);
619 FreeStrBuf(&first_tok);
623 MSG_syslog(LOG_DEBUG, " after update: %s\n", ChrPtr(vset));
625 /* Decide which message set we're manipulating */
627 case ctdlsetseen_seen:
628 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
630 case ctdlsetseen_answered:
631 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
637 CtdlSetRelationship(&vbuf, which_user, which_room);
643 * API function to perform an operation for each qualifying message in the
644 * current room. (Returns the number of messages processed.)
646 int CtdlForEachMessage(int mode, long ref, char *search_string,
648 struct CtdlMessage *compare,
649 ForEachMsgCallback CallBack,
652 struct CitContext *CCC = CC;
655 struct cdbdata *cdbfr;
656 long *msglist = NULL;
658 int num_processed = 0;
661 struct CtdlMessage *msg = NULL;
664 int printed_lastold = 0;
665 int num_search_msgs = 0;
666 long *search_msgs = NULL;
668 int need_to_free_re = 0;
671 if ((content_type) && (!IsEmptyStr(content_type))) {
672 regcomp(&re, content_type, 0);
676 /* Learn about the user and room in question */
677 if (server_shutting_down) {
678 if (need_to_free_re) regfree(&re);
681 CtdlGetUser(&CCC->user, CCC->curr_user);
683 if (server_shutting_down) {
684 if (need_to_free_re) regfree(&re);
687 CtdlGetRelationship(&vbuf, &CCC->user, &CCC->room);
689 if (server_shutting_down) {
690 if (need_to_free_re) regfree(&re);
694 /* Load the message list */
695 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
697 if (need_to_free_re) regfree(&re);
698 return 0; /* No messages at all? No further action. */
701 msglist = (long *) cdbfr->ptr;
702 num_msgs = cdbfr->len / sizeof(long);
704 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
705 cdb_free(cdbfr); /* we own this memory now */
708 * Now begin the traversal.
710 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
712 /* If the caller is looking for a specific MIME type, filter
713 * out all messages which are not of the type requested.
715 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
717 /* This call to GetMetaData() sits inside this loop
718 * so that we only do the extra database read per msg
719 * if we need to. Doing the extra read all the time
720 * really kills the server. If we ever need to use
721 * metadata for another search criterion, we need to
722 * move the read somewhere else -- but still be smart
723 * enough to only do the read if the caller has
724 * specified something that will need it.
726 if (server_shutting_down) {
727 if (need_to_free_re) regfree(&re);
731 GetMetaData(&smi, msglist[a]);
733 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
734 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
740 num_msgs = sort_msglist(msglist, num_msgs);
742 /* If a template was supplied, filter out the messages which
743 * don't match. (This could induce some delays!)
746 if (compare != NULL) {
747 for (a = 0; a < num_msgs; ++a) {
748 if (server_shutting_down) {
749 if (need_to_free_re) regfree(&re);
753 msg = CtdlFetchMessage(msglist[a], 1);
755 if (CtdlMsgCmp(msg, compare)) {
758 CtdlFreeMessage(msg);
764 /* If a search string was specified, get a message list from
765 * the full text index and remove messages which aren't on both
769 * Since the lists are sorted and strictly ascending, and the
770 * output list is guaranteed to be shorter than or equal to the
771 * input list, we overwrite the bottom of the input list. This
772 * eliminates the need to memmove big chunks of the list over and
775 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
777 /* Call search module via hook mechanism.
778 * NULL means use any search function available.
779 * otherwise replace with a char * to name of search routine
781 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
783 if (num_search_msgs > 0) {
787 orig_num_msgs = num_msgs;
789 for (i=0; i<orig_num_msgs; ++i) {
790 for (j=0; j<num_search_msgs; ++j) {
791 if (msglist[i] == search_msgs[j]) {
792 msglist[num_msgs++] = msglist[i];
798 num_msgs = 0; /* No messages qualify */
800 if (search_msgs != NULL) free(search_msgs);
802 /* Now that we've purged messages which don't contain the search
803 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
810 * Now iterate through the message list, according to the
811 * criteria supplied by the caller.
814 for (a = 0; a < num_msgs; ++a) {
815 if (server_shutting_down) {
816 if (need_to_free_re) regfree(&re);
818 return num_processed;
820 thismsg = msglist[a];
821 if (mode == MSGS_ALL) {
825 is_seen = is_msg_in_sequence_set(
826 vbuf.v_seen, thismsg);
827 if (is_seen) lastold = thismsg;
833 || ((mode == MSGS_OLD) && (is_seen))
834 || ((mode == MSGS_NEW) && (!is_seen))
835 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
836 || ((mode == MSGS_FIRST) && (a < ref))
837 || ((mode == MSGS_GT) && (thismsg > ref))
838 || ((mode == MSGS_LT) && (thismsg < ref))
839 || ((mode == MSGS_EQ) && (thismsg == ref))
842 if ((mode == MSGS_NEW) && (CCC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
844 CallBack(lastold, userdata);
848 if (CallBack) CallBack(thismsg, userdata);
852 if (need_to_free_re) regfree(&re);
855 * We cache the most recent msglist in order to do security checks later
857 if (CCC->client_socket > 0) {
858 if (CCC->cached_msglist != NULL) {
859 free(CCC->cached_msglist);
861 CCC->cached_msglist = msglist;
862 CCC->cached_num_msgs = num_msgs;
868 return num_processed;
874 * cmd_msgs() - get list of message #'s in this room
875 * implements the MSGS server command using CtdlForEachMessage()
877 void cmd_msgs(char *cmdbuf)
886 int with_template = 0;
887 struct CtdlMessage *template = NULL;
888 char search_string[1024];
889 ForEachMsgCallback CallBack;
891 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
893 extract_token(which, cmdbuf, 0, '|', sizeof which);
894 cm_ref = extract_int(cmdbuf, 1);
895 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
896 with_template = extract_int(cmdbuf, 2);
897 switch (extract_int(cmdbuf, 3))
901 CallBack = simple_listing;
904 CallBack = headers_listing;
907 CallBack = headers_euid;
912 if (!strncasecmp(which, "OLD", 3))
914 else if (!strncasecmp(which, "NEW", 3))
916 else if (!strncasecmp(which, "FIRST", 5))
918 else if (!strncasecmp(which, "LAST", 4))
920 else if (!strncasecmp(which, "GT", 2))
922 else if (!strncasecmp(which, "LT", 2))
924 else if (!strncasecmp(which, "SEARCH", 6))
929 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
930 cprintf("%d Full text index is not enabled on this server.\n",
931 ERROR + CMD_NOT_SUPPORTED);
937 cprintf("%d Send template then receive message list\n",
939 template = (struct CtdlMessage *)
940 malloc(sizeof(struct CtdlMessage));
941 memset(template, 0, sizeof(struct CtdlMessage));
942 template->cm_magic = CTDLMESSAGE_MAGIC;
943 template->cm_anon_type = MES_NORMAL;
945 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
946 extract_token(tfield, buf, 0, '|', sizeof tfield);
947 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
948 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
949 if (!strcasecmp(tfield, msgkeys[i])) {
950 template->cm_fields[i] =
958 cprintf("%d \n", LISTING_FOLLOWS);
961 CtdlForEachMessage(mode,
962 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
963 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
968 if (template != NULL) CtdlFreeMessage(template);
976 * help_subst() - support routine for help file viewer
978 void help_subst(char *strbuf, char *source, char *dest)
983 while (p = pattern2(strbuf, source), (p >= 0)) {
984 strcpy(workbuf, &strbuf[p + strlen(source)]);
985 strcpy(&strbuf[p], dest);
986 strcat(strbuf, workbuf);
991 void do_help_subst(char *buffer)
995 help_subst(buffer, "^nodename", config.c_nodename);
996 help_subst(buffer, "^humannode", config.c_humannode);
997 help_subst(buffer, "^fqdn", config.c_fqdn);
998 help_subst(buffer, "^username", CC->user.fullname);
999 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
1000 help_subst(buffer, "^usernum", buf2);
1001 help_subst(buffer, "^sysadm", config.c_sysadm);
1002 help_subst(buffer, "^variantname", CITADEL);
1003 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
1004 help_subst(buffer, "^maxsessions", buf2);
1005 help_subst(buffer, "^bbsdir", ctdl_message_dir);
1011 * memfmout() - Citadel text formatter and paginator.
1012 * Although the original purpose of this routine was to format
1013 * text to the reader's screen width, all we're really using it
1014 * for here is to format text out to 80 columns before sending it
1015 * to the client. The client software may reformat it again.
1018 char *mptr, /* where are we going to get our text from? */
1019 const char *nl /* string to terminate lines with */
1021 struct CitContext *CCC = CC;
1023 unsigned char ch = 0;
1030 while (ch=*(mptr++), ch != 0) {
1033 if (client_write(outbuf, len) == -1)
1035 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1039 if (client_write(nl, nllen) == -1)
1041 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1046 else if (ch == '\r') {
1047 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
1049 else if (isspace(ch)) {
1050 if (column > 72) { /* Beyond 72 columns, break on the next space */
1051 if (client_write(outbuf, len) == -1)
1053 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1057 if (client_write(nl, nllen) == -1)
1059 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1072 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
1073 if (client_write(outbuf, len) == -1)
1075 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1079 if (client_write(nl, nllen) == -1)
1081 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1089 if (client_write(outbuf, len) == -1)
1091 MSGM_syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1095 client_write(nl, nllen);
1103 * Callback function for mime parser that simply lists the part
1105 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1106 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1107 char *cbid, void *cbuserdata)
1111 ma = (struct ma_info *)cbuserdata;
1112 if (ma->is_ma == 0) {
1113 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1126 * Callback function for multipart prefix
1128 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1129 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1130 char *cbid, void *cbuserdata)
1134 ma = (struct ma_info *)cbuserdata;
1135 if (!strcasecmp(cbtype, "multipart/alternative")) {
1139 if (ma->is_ma == 0) {
1140 cprintf("pref=%s|%s\n", partnum, cbtype);
1145 * Callback function for multipart sufffix
1147 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1148 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1149 char *cbid, void *cbuserdata)
1153 ma = (struct ma_info *)cbuserdata;
1154 if (ma->is_ma == 0) {
1155 cprintf("suff=%s|%s\n", partnum, cbtype);
1157 if (!strcasecmp(cbtype, "multipart/alternative")) {
1164 * Callback function for mime parser that opens a section for downloading
1166 void mime_download(char *name, char *filename, char *partnum, char *disp,
1167 void *content, char *cbtype, char *cbcharset, size_t length,
1168 char *encoding, char *cbid, void *cbuserdata)
1171 CitContext *CCC = MyContext();
1173 /* Silently go away if there's already a download open. */
1174 if (CCC->download_fp != NULL)
1178 (!IsEmptyStr(partnum) && (!strcasecmp(CCC->download_desired_section, partnum)))
1179 || (!IsEmptyStr(cbid) && (!strcasecmp(CCC->download_desired_section, cbid)))
1181 CCC->download_fp = tmpfile();
1182 if (CCC->download_fp == NULL) {
1183 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1185 cprintf("%d cannot open temporary file: %s\n",
1186 ERROR + INTERNAL_ERROR, strerror(errno));
1190 rv = fwrite(content, length, 1, CCC->download_fp);
1192 MSG_syslog(LOG_EMERG, "mime_download(): Couldn't write: %s\n",
1194 cprintf("%d unable to write tempfile.\n",
1196 fclose(CCC->download_fp);
1197 CCC->download_fp = NULL;
1200 fflush(CCC->download_fp);
1201 rewind(CCC->download_fp);
1203 OpenCmdResult(filename, cbtype);
1210 * Callback function for mime parser that outputs a section all at once.
1211 * We can specify the desired section by part number *or* content-id.
1213 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1214 void *content, char *cbtype, char *cbcharset, size_t length,
1215 char *encoding, char *cbid, void *cbuserdata)
1217 int *found_it = (int *)cbuserdata;
1220 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1221 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1224 cprintf("%d %d|-1|%s|%s|%s\n",
1231 client_write(content, length);
1237 * Load a message from disk into memory.
1238 * This is used by CtdlOutputMsg() and other fetch functions.
1240 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1241 * using the CtdlMessageFree() function.
1243 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1245 struct CitContext *CCC = CC;
1246 struct cdbdata *dmsgtext;
1247 struct CtdlMessage *ret = NULL;
1251 cit_uint8_t field_header;
1253 MSG_syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1254 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1255 if (dmsgtext == NULL) {
1256 MSG_syslog(LOG_ERR, "CtdlFetchMessage(%ld, %d) Failed!\n", msgnum, with_body);
1259 mptr = dmsgtext->ptr;
1260 upper_bound = mptr + dmsgtext->len;
1262 /* Parse the three bytes that begin EVERY message on disk.
1263 * The first is always 0xFF, the on-disk magic number.
1264 * The second is the anonymous/public type byte.
1265 * The third is the format type byte (vari, fixed, or MIME).
1269 MSG_syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1273 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1274 memset(ret, 0, sizeof(struct CtdlMessage));
1276 ret->cm_magic = CTDLMESSAGE_MAGIC;
1277 ret->cm_anon_type = *mptr++; /* Anon type byte */
1278 ret->cm_format_type = *mptr++; /* Format type byte */
1281 * The rest is zero or more arbitrary fields. Load them in.
1282 * We're done when we encounter either a zero-length field or
1283 * have just processed the 'M' (message text) field.
1286 if (mptr >= upper_bound) {
1289 field_header = *mptr++;
1290 ret->cm_fields[field_header] = strdup(mptr);
1292 while (*mptr++ != 0); /* advance to next field */
1294 } while ((mptr < upper_bound) && (field_header != 'M'));
1298 /* Always make sure there's something in the msg text field. If
1299 * it's NULL, the message text is most likely stored separately,
1300 * so go ahead and fetch that. Failing that, just set a dummy
1301 * body so other code doesn't barf.
1303 if ( (ret->cm_fields[eMesageText] == NULL) && (with_body) ) {
1304 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1305 if (dmsgtext != NULL) {
1306 ret->cm_fields[eMesageText] = dmsgtext->ptr;
1307 dmsgtext->ptr = NULL;
1311 if (ret->cm_fields[eMesageText] == NULL) {
1312 ret->cm_fields[eMesageText] = strdup("\r\n\r\n (no text)\r\n");
1315 /* Perform "before read" hooks (aborting if any return nonzero) */
1316 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1317 CtdlFreeMessage(ret);
1326 * Returns 1 if the supplied pointer points to a valid Citadel message.
1327 * If the pointer is NULL or the magic number check fails, returns 0.
1329 int is_valid_message(struct CtdlMessage *msg) {
1332 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1333 struct CitContext *CCC = CC;
1334 MSGM_syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1340 void CtdlFreeMessageContents(struct CtdlMessage *msg)
1344 for (i = 0; i < 256; ++i)
1345 if (msg->cm_fields[i] != NULL) {
1346 free(msg->cm_fields[i]);
1349 msg->cm_magic = 0; /* just in case */
1352 * 'Destructor' for struct CtdlMessage
1354 void CtdlFreeMessage(struct CtdlMessage *msg)
1356 if (is_valid_message(msg) == 0)
1358 if (msg != NULL) free (msg);
1361 CtdlFreeMessageContents(msg);
1365 int DupCMField(int i, struct CtdlMessage *OrgMsg, struct CtdlMessage *NewMsg)
1368 len = strlen(OrgMsg->cm_fields[i]);
1369 NewMsg->cm_fields[i] = malloc(len + 1);
1370 if (NewMsg->cm_fields[i] == NULL)
1372 memcpy(NewMsg->cm_fields[i], OrgMsg->cm_fields[i], len);
1373 NewMsg->cm_fields[i][len] = '\0';
1377 struct CtdlMessage * CtdlDuplicateMessage(struct CtdlMessage *OrgMsg)
1380 struct CtdlMessage *NewMsg;
1382 if (is_valid_message(OrgMsg) == 0)
1384 NewMsg = (struct CtdlMessage *)malloc(sizeof(struct CtdlMessage));
1388 memcpy(NewMsg, OrgMsg, sizeof(struct CtdlMessage));
1390 memset(&NewMsg->cm_fields, 0, sizeof(char*) * 256);
1392 for (i = 0; i < 256; ++i)
1394 if (OrgMsg->cm_fields[i] != NULL)
1396 if (!DupCMField(i, OrgMsg, NewMsg))
1398 CtdlFreeMessage(NewMsg);
1410 * Pre callback function for multipart/alternative
1412 * NOTE: this differs from the standard behavior for a reason. Normally when
1413 * displaying multipart/alternative you want to show the _last_ usable
1414 * format in the message. Here we show the _first_ one, because it's
1415 * usually text/plain. Since this set of functions is designed for text
1416 * output to non-MIME-aware clients, this is the desired behavior.
1419 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1420 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1421 char *cbid, void *cbuserdata)
1423 struct CitContext *CCC = CC;
1426 ma = (struct ma_info *)cbuserdata;
1427 MSG_syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1428 if (!strcasecmp(cbtype, "multipart/alternative")) {
1432 if (!strcasecmp(cbtype, "message/rfc822")) {
1438 * Post callback function for multipart/alternative
1440 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1441 void *content, char *cbtype, char *cbcharset, size_t length,
1442 char *encoding, char *cbid, void *cbuserdata)
1444 struct CitContext *CCC = CC;
1447 ma = (struct ma_info *)cbuserdata;
1448 MSG_syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1449 if (!strcasecmp(cbtype, "multipart/alternative")) {
1453 if (!strcasecmp(cbtype, "message/rfc822")) {
1459 * Inline callback function for mime parser that wants to display text
1461 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1462 void *content, char *cbtype, char *cbcharset, size_t length,
1463 char *encoding, char *cbid, void *cbuserdata)
1465 struct CitContext *CCC = CC;
1471 ma = (struct ma_info *)cbuserdata;
1473 MSG_syslog(LOG_DEBUG,
1474 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1475 partnum, filename, cbtype, (long)length);
1478 * If we're in the middle of a multipart/alternative scope and
1479 * we've already printed another section, skip this one.
1481 if ( (ma->is_ma) && (ma->did_print) ) {
1482 MSG_syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1487 if ( (!strcasecmp(cbtype, "text/plain"))
1488 || (IsEmptyStr(cbtype)) ) {
1491 client_write(wptr, length);
1492 if (wptr[length-1] != '\n') {
1499 if (!strcasecmp(cbtype, "text/html")) {
1500 ptr = html_to_ascii(content, length, 80, 0);
1502 client_write(ptr, wlen);
1503 if ((wlen > 0) && (ptr[wlen-1] != '\n')) {
1510 if (ma->use_fo_hooks) {
1511 if (PerformFixedOutputHooks(cbtype, content, length)) {
1512 /* above function returns nonzero if it handled the part */
1517 if (strncasecmp(cbtype, "multipart/", 10)) {
1518 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1519 partnum, filename, cbtype, (long)length);
1525 * The client is elegant and sophisticated and wants to be choosy about
1526 * MIME content types, so figure out which multipart/alternative part
1527 * we're going to send.
1529 * We use a system of weights. When we find a part that matches one of the
1530 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1531 * and then set ma->chosen_pref to that MIME type's position in our preference
1532 * list. If we then hit another match, we only replace the first match if
1533 * the preference value is lower.
1535 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1536 void *content, char *cbtype, char *cbcharset, size_t length,
1537 char *encoding, char *cbid, void *cbuserdata)
1539 struct CitContext *CCC = CC;
1544 ma = (struct ma_info *)cbuserdata;
1546 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1547 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1548 // I don't know if there are any side effects! Please TEST TEST TEST
1549 //if (ma->is_ma > 0) {
1551 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1552 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1553 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1554 if (i < ma->chosen_pref) {
1555 MSG_syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1556 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1557 ma->chosen_pref = i;
1564 * Now that we've chosen our preferred part, output it.
1566 void output_preferred(char *name,
1578 struct CitContext *CCC = CC;
1581 int add_newline = 0;
1584 char *decoded = NULL;
1585 size_t bytes_decoded;
1588 ma = (struct ma_info *)cbuserdata;
1590 /* This is not the MIME part you're looking for... */
1591 if (strcasecmp(partnum, ma->chosen_part)) return;
1593 /* If the content-type of this part is in our preferred formats
1594 * list, we can simply output it verbatim.
1596 for (i=0; i<num_tokens(CCC->preferred_formats, '|'); ++i) {
1597 extract_token(buf, CCC->preferred_formats, i, '|', sizeof buf);
1598 if (!strcasecmp(buf, cbtype)) {
1599 /* Yeah! Go! W00t!! */
1600 if (ma->dont_decode == 0)
1601 rc = mime_decode_now (content,
1607 break; /* Give us the chance, maybe theres another one. */
1609 if (rc == 0) text_content = (char *)content;
1611 text_content = decoded;
1612 length = bytes_decoded;
1615 if (text_content[length-1] != '\n') {
1618 cprintf("Content-type: %s", cbtype);
1619 if (!IsEmptyStr(cbcharset)) {
1620 cprintf("; charset=%s", cbcharset);
1622 cprintf("\nContent-length: %d\n",
1623 (int)(length + add_newline) );
1624 if (!IsEmptyStr(encoding)) {
1625 cprintf("Content-transfer-encoding: %s\n", encoding);
1628 cprintf("Content-transfer-encoding: 7bit\n");
1630 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1632 if (client_write(text_content, length) == -1)
1634 MSGM_syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1637 if (add_newline) cprintf("\n");
1638 if (decoded != NULL) free(decoded);
1643 /* No translations required or possible: output as text/plain */
1644 cprintf("Content-type: text/plain\n\n");
1646 if (ma->dont_decode == 0)
1647 rc = mime_decode_now (content,
1653 return; /* Give us the chance, maybe theres another one. */
1655 if (rc == 0) text_content = (char *)content;
1657 text_content = decoded;
1658 length = bytes_decoded;
1661 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1662 length, encoding, cbid, cbuserdata);
1663 if (decoded != NULL) free(decoded);
1668 char desired_section[64];
1675 * Callback function for
1677 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1678 void *content, char *cbtype, char *cbcharset, size_t length,
1679 char *encoding, char *cbid, void *cbuserdata)
1681 struct encapmsg *encap;
1683 encap = (struct encapmsg *)cbuserdata;
1685 /* Only proceed if this is the desired section... */
1686 if (!strcasecmp(encap->desired_section, partnum)) {
1687 encap->msglen = length;
1688 encap->msg = malloc(length + 2);
1689 memcpy(encap->msg, content, length);
1696 * Determine whether the specified message exists in the cached_msglist
1697 * (This is a security check)
1699 int check_cached_msglist(long msgnum) {
1700 struct CitContext *CCC = CC;
1702 /* cases in which we skip the check */
1703 if (!CCC) return om_ok; /* not a session */
1704 if (CCC->client_socket <= 0) return om_ok; /* not a client session */
1705 if (CCC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1706 if (CCC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1709 /* Do a binary search within the cached_msglist for the requested msgnum */
1711 int max = (CC->cached_num_msgs - 1);
1713 while (max >= min) {
1714 int middle = min + (max-min) / 2 ;
1715 if (msgnum == CCC->cached_msglist[middle]) {
1718 if (msgnum > CC->cached_msglist[middle]) {
1726 return om_access_denied;
1731 * Determine whether the currently logged in session has permission to read
1732 * messages in the current room.
1734 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1735 if ( (!(CC->logged_in))
1736 && (!(CC->internal_pgm))
1737 && (!config.c_guest_logins)
1739 return(om_not_logged_in);
1746 * Get a message off disk. (returns om_* values found in msgbase.h)
1749 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1750 int mode, /* how would you like that message? */
1751 int headers_only, /* eschew the message body? */
1752 int do_proto, /* do Citadel protocol responses? */
1753 int crlf, /* Use CRLF newlines instead of LF? */
1754 char *section, /* NULL or a message/rfc822 section */
1755 int flags, /* various flags; see msgbase.h */
1759 struct CitContext *CCC = CC;
1760 struct CtdlMessage *TheMessage = NULL;
1761 int retcode = CIT_OK;
1762 struct encapmsg encap;
1765 MSG_syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1767 (section ? section : "<>")
1770 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1773 if (r == om_not_logged_in) {
1774 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1777 cprintf("%d An unknown error has occurred.\n", ERROR);
1784 * Check to make sure the message is actually IN this room
1786 r = check_cached_msglist(msg_num);
1787 if (r == om_access_denied) {
1788 /* Not in the cache? We get ONE shot to check it again. */
1789 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1790 r = check_cached_msglist(msg_num);
1793 MSG_syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1794 msg_num, CCC->room.QRname
1797 if (r == om_access_denied) {
1798 cprintf("%d message %ld was not found in this room\n",
1799 ERROR + HIGHER_ACCESS_REQUIRED,
1808 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1809 * request that we don't even bother loading the body into memory.
1811 if (headers_only == HEADERS_FAST) {
1812 TheMessage = CtdlFetchMessage(msg_num, 0);
1815 TheMessage = CtdlFetchMessage(msg_num, 1);
1818 if (TheMessage == NULL) {
1819 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1820 ERROR + MESSAGE_NOT_FOUND, msg_num);
1821 return(om_no_such_msg);
1824 /* Here is the weird form of this command, to process only an
1825 * encapsulated message/rfc822 section.
1827 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1828 memset(&encap, 0, sizeof encap);
1829 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1830 mime_parser(TheMessage->cm_fields[eMesageText],
1832 *extract_encapsulated_message,
1833 NULL, NULL, (void *)&encap, 0
1836 if ((Author != NULL) && (*Author == NULL))
1838 *Author = TheMessage->cm_fields[eAuthor];
1839 TheMessage->cm_fields[eAuthor] = NULL;
1841 if ((Address != NULL) && (*Address == NULL))
1843 *Address = TheMessage->cm_fields[erFc822Addr];
1844 TheMessage->cm_fields[erFc822Addr] = NULL;
1846 CtdlFreeMessage(TheMessage);
1850 encap.msg[encap.msglen] = 0;
1851 TheMessage = convert_internet_message(encap.msg);
1852 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1854 /* Now we let it fall through to the bottom of this
1855 * function, because TheMessage now contains the
1856 * encapsulated message instead of the top-level
1857 * message. Isn't that neat?
1863 cprintf("%d msg %ld has no part %s\n",
1864 ERROR + MESSAGE_NOT_FOUND,
1868 retcode = om_no_such_msg;
1873 /* Ok, output the message now */
1874 if (retcode == CIT_OK)
1875 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1876 if ((Author != NULL) && (*Author == NULL))
1878 *Author = TheMessage->cm_fields[eAuthor];
1879 TheMessage->cm_fields[eAuthor] = NULL;
1881 if ((Address != NULL) && (*Address == NULL))
1883 *Address = TheMessage->cm_fields[erFc822Addr];
1884 TheMessage->cm_fields[erFc822Addr] = NULL;
1887 CtdlFreeMessage(TheMessage);
1893 char *qp_encode_email_addrs(char *source)
1895 struct CitContext *CCC = CC;
1896 char *user, *node, *name;
1897 const char headerStr[] = "=?UTF-8?Q?";
1901 int need_to_encode = 0;
1907 long nAddrPtrMax = 50;
1912 if (source == NULL) return source;
1913 if (IsEmptyStr(source)) return source;
1914 if (MessageDebugEnabled != 0) cit_backtrace();
1915 MSG_syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1917 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1918 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1919 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1922 while (!IsEmptyStr (&source[i])) {
1923 if (nColons >= nAddrPtrMax){
1926 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1927 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1928 free (AddrPtr), AddrPtr = ptr;
1930 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1931 memset(&ptr[nAddrPtrMax], 0,
1932 sizeof (long) * nAddrPtrMax);
1934 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1935 free (AddrUtf8), AddrUtf8 = ptr;
1938 if (((unsigned char) source[i] < 32) ||
1939 ((unsigned char) source[i] > 126)) {
1941 AddrUtf8[nColons] = 1;
1943 if (source[i] == '"')
1944 InQuotes = !InQuotes;
1945 if (!InQuotes && source[i] == ',') {
1946 AddrPtr[nColons] = i;
1951 if (need_to_encode == 0) {
1958 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1959 Encoded = (char*) malloc (EncodedMaxLen);
1961 for (i = 0; i < nColons; i++)
1962 source[AddrPtr[i]++] = '\0';
1963 /* TODO: if libidn, this might get larger*/
1964 user = malloc(SourceLen + 1);
1965 node = malloc(SourceLen + 1);
1966 name = malloc(SourceLen + 1);
1970 for (i = 0; i < nColons && nPtr != NULL; i++) {
1971 nmax = EncodedMaxLen - (nPtr - Encoded);
1973 process_rfc822_addr(&source[AddrPtr[i]],
1977 /* TODO: libIDN here ! */
1978 if (IsEmptyStr(name)) {
1979 n = snprintf(nPtr, nmax,
1980 (i==0)?"%s@%s" : ",%s@%s",
1984 EncodedName = rfc2047encode(name, strlen(name));
1985 n = snprintf(nPtr, nmax,
1986 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1987 EncodedName, user, node);
1992 n = snprintf(nPtr, nmax,
1993 (i==0)?"%s" : ",%s",
1994 &source[AddrPtr[i]]);
2000 ptr = (char*) malloc(EncodedMaxLen * 2);
2001 memcpy(ptr, Encoded, EncodedMaxLen);
2002 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
2003 free(Encoded), Encoded = ptr;
2005 i--; /* do it once more with properly lengthened buffer */
2008 for (i = 0; i < nColons; i++)
2009 source[--AddrPtr[i]] = ',';
2020 /* If the last item in a list of recipients was truncated to a partial address,
2021 * remove it completely in order to avoid choking libSieve
2023 void sanitize_truncated_recipient(char *str)
2026 if (num_tokens(str, ',') < 2) return;
2028 int len = strlen(str);
2029 if (len < 900) return;
2030 if (len > 998) str[998] = 0;
2032 char *cptr = strrchr(str, ',');
2035 char *lptr = strchr(cptr, '<');
2036 char *rptr = strchr(cptr, '>');
2038 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
2044 void OutputCtdlMsgHeaders(
2045 struct CtdlMessage *TheMessage,
2046 int do_proto) /* do Citadel protocol responses? */
2051 char display_name[256];
2053 /* begin header processing loop for Citadel message format */
2054 safestrncpy(display_name, "<unknown>", sizeof display_name);
2055 if (TheMessage->cm_fields[eAuthor]) {
2056 strcpy(buf, TheMessage->cm_fields[eAuthor]);
2057 if (TheMessage->cm_anon_type == MES_ANONONLY) {
2058 safestrncpy(display_name, "****", sizeof display_name);
2060 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
2061 safestrncpy(display_name, "anonymous", sizeof display_name);
2064 safestrncpy(display_name, buf, sizeof display_name);
2066 if ((is_room_aide())
2067 && ((TheMessage->cm_anon_type == MES_ANONONLY)
2068 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
2069 size_t tmp = strlen(display_name);
2070 snprintf(&display_name[tmp],
2071 sizeof display_name - tmp,
2076 /* Don't show Internet address for users on the
2077 * local Citadel network.
2080 if (TheMessage->cm_fields[eNodeName] != NULL)
2081 if (!IsEmptyStr(TheMessage->cm_fields[eNodeName]))
2082 if (haschar(TheMessage->cm_fields[eNodeName], '.') == 0) {
2086 /* Now spew the header fields in the order we like them. */
2087 for (i=0; i< NDiskFields; ++i) {
2089 Field = FieldOrder[i];
2090 if (Field != eMesageText) {
2091 if ( (TheMessage->cm_fields[Field] != NULL)
2092 && (msgkeys[Field] != NULL) ) {
2093 if ((Field == eenVelopeTo) ||
2094 (Field == eRecipient) ||
2095 (Field == eCarbonCopY)) {
2096 sanitize_truncated_recipient(TheMessage->cm_fields[Field]);
2098 if (Field == eAuthor) {
2099 if (do_proto) cprintf("%s=%s\n",
2103 else if ((Field == erFc822Addr) && (suppress_f)) {
2106 /* Masquerade display name if needed */
2108 if (do_proto) cprintf("%s=%s\n",
2110 TheMessage->cm_fields[Field]
2119 void OutputRFC822MsgHeaders(
2120 struct CtdlMessage *TheMessage,
2121 int flags, /* should the bessage be exported clean */
2123 char *mid, long sizeof_mid,
2124 char *suser, long sizeof_suser,
2125 char *luser, long sizeof_luser,
2126 char *fuser, long sizeof_fuser,
2127 char *snode, long sizeof_snode)
2129 char datestamp[100];
2130 int subject_found = 0;
2137 for (i = 0; i < 256; ++i) {
2138 if (TheMessage->cm_fields[i]) {
2139 mptr = mpptr = TheMessage->cm_fields[i];
2142 safestrncpy(luser, mptr, sizeof_luser);
2143 safestrncpy(suser, mptr, sizeof_suser);
2145 else if (i == 'Y') {
2146 if ((flags & QP_EADDR) != 0) {
2147 mptr = qp_encode_email_addrs(mptr);
2149 sanitize_truncated_recipient(mptr);
2150 cprintf("CC: %s%s", mptr, nl);
2152 else if (i == 'P') {
2153 cprintf("Return-Path: %s%s", mptr, nl);
2155 else if (i == eListID) {
2156 cprintf("List-ID: %s%s", mptr, nl);
2158 else if (i == 'V') {
2159 if ((flags & QP_EADDR) != 0)
2160 mptr = qp_encode_email_addrs(mptr);
2162 while ((*hptr != '\0') && isspace(*hptr))
2164 if (!IsEmptyStr(hptr))
2165 cprintf("Envelope-To: %s%s", hptr, nl);
2167 else if (i == 'U') {
2168 cprintf("Subject: %s%s", mptr, nl);
2172 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
2173 else if (i == erFc822Addr)
2174 safestrncpy(fuser, mptr, sizeof_fuser);
2175 /* else if (i == 'O')
2176 cprintf("X-Citadel-Room: %s%s",
2179 safestrncpy(snode, mptr, sizeof_snode);
2182 if (haschar(mptr, '@') == 0)
2184 sanitize_truncated_recipient(mptr);
2185 cprintf("To: %s@%s", mptr, config.c_fqdn);
2190 if ((flags & QP_EADDR) != 0) {
2191 mptr = qp_encode_email_addrs(mptr);
2193 sanitize_truncated_recipient(mptr);
2194 cprintf("To: %s", mptr);
2198 else if (i == 'T') {
2199 datestring(datestamp, sizeof datestamp,
2200 atol(mptr), DATESTRING_RFC822);
2201 cprintf("Date: %s%s", datestamp, nl);
2203 else if (i == 'W') {
2204 cprintf("References: ");
2205 k = num_tokens(mptr, '|');
2206 for (j=0; j<k; ++j) {
2207 extract_token(buf, mptr, j, '|', sizeof buf);
2208 cprintf("<%s>", buf);
2217 else if (i == eReplyTo) {
2219 while ((*hptr != '\0') && isspace(*hptr))
2221 if (!IsEmptyStr(hptr))
2222 cprintf("Reply-To: %s%s", mptr, nl);
2228 if (subject_found == 0) {
2229 cprintf("Subject: (no subject)%s", nl);
2234 void Dump_RFC822HeadersBody(
2235 struct CtdlMessage *TheMessage,
2236 int headers_only, /* eschew the message body? */
2237 int flags, /* should the bessage be exported clean? */
2241 cit_uint8_t prev_ch;
2243 const char *StartOfText = StrBufNOTNULL;
2246 int nllen = strlen(nl);
2249 mptr = TheMessage->cm_fields[eMesageText];
2253 while (*mptr != '\0') {
2254 if (*mptr == '\r') {
2261 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2263 eoh = *(mptr+1) == '\n';
2267 StartOfText = strchr(StartOfText, '\n');
2268 StartOfText = strchr(StartOfText, '\n');
2271 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2272 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2273 ((headers_only != HEADERS_NONE) &&
2274 (headers_only != HEADERS_ONLY))
2276 if (*mptr == '\n') {
2277 memcpy(&outbuf[outlen], nl, nllen);
2279 outbuf[outlen] = '\0';
2282 outbuf[outlen++] = *mptr;
2286 if (flags & ESC_DOT)
2288 if ((prev_ch == '\n') &&
2290 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2292 outbuf[outlen++] = '.';
2297 if (outlen > 1000) {
2298 if (client_write(outbuf, outlen) == -1)
2300 struct CitContext *CCC = CC;
2301 MSGM_syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2308 client_write(outbuf, outlen);
2314 /* If the format type on disk is 1 (fixed-format), then we want
2315 * everything to be output completely literally ... regardless of
2316 * what message transfer format is in use.
2318 void DumpFormatFixed(
2319 struct CtdlMessage *TheMessage,
2320 int mode, /* how would you like that message? */
2327 int nllen = strlen (nl);
2330 mptr = TheMessage->cm_fields[eMesageText];
2332 if (mode == MT_MIME) {
2333 cprintf("Content-type: text/plain\n\n");
2337 while (ch = *mptr++, ch > 0) {
2341 if ((buflen > 250) && (!xlline)){
2345 while ((buflen > 0) &&
2346 (!isspace(buf[buflen])))
2352 mptr -= tbuflen - buflen;
2357 /* if we reach the outer bounds of our buffer,
2358 abort without respect what whe purge. */
2361 (buflen > SIZ - nllen - 2)))
2365 memcpy (&buf[buflen], nl, nllen);
2369 if (client_write(buf, buflen) == -1)
2371 struct CitContext *CCC = CC;
2372 MSGM_syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2384 if (!IsEmptyStr(buf))
2385 cprintf("%s%s", buf, nl);
2389 * Get a message off disk. (returns om_* values found in msgbase.h)
2391 int CtdlOutputPreLoadedMsg(
2392 struct CtdlMessage *TheMessage,
2393 int mode, /* how would you like that message? */
2394 int headers_only, /* eschew the message body? */
2395 int do_proto, /* do Citadel protocol responses? */
2396 int crlf, /* Use CRLF newlines instead of LF? */
2397 int flags /* should the bessage be exported clean? */
2399 struct CitContext *CCC = CC;
2402 const char *nl; /* newline string */
2405 /* Buffers needed for RFC822 translation. These are all filled
2406 * using functions that are bounds-checked, and therefore we can
2407 * make them substantially smaller than SIZ.
2415 MSG_syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2416 ((TheMessage == NULL) ? "NULL" : "not null"),
2417 mode, headers_only, do_proto, crlf);
2419 strcpy(mid, "unknown");
2420 nl = (crlf ? "\r\n" : "\n");
2422 if (!is_valid_message(TheMessage)) {
2423 MSGM_syslog(LOG_ERR,
2424 "ERROR: invalid preloaded message for output\n");
2426 return(om_no_such_msg);
2429 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2430 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2432 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields[eenVelopeTo] != NULL) ) {
2433 memset(TheMessage->cm_fields[eenVelopeTo], ' ', strlen(TheMessage->cm_fields[eenVelopeTo]));
2436 /* Are we downloading a MIME component? */
2437 if (mode == MT_DOWNLOAD) {
2438 if (TheMessage->cm_format_type != FMT_RFC822) {
2440 cprintf("%d This is not a MIME message.\n",
2441 ERROR + ILLEGAL_VALUE);
2442 } else if (CCC->download_fp != NULL) {
2443 if (do_proto) cprintf(
2444 "%d You already have a download open.\n",
2445 ERROR + RESOURCE_BUSY);
2447 /* Parse the message text component */
2448 mptr = TheMessage->cm_fields[eMesageText];
2449 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2450 /* If there's no file open by this time, the requested
2451 * section wasn't found, so print an error
2453 if (CCC->download_fp == NULL) {
2454 if (do_proto) cprintf(
2455 "%d Section %s not found.\n",
2456 ERROR + FILE_NOT_FOUND,
2457 CCC->download_desired_section);
2460 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2463 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2464 * in a single server operation instead of opening a download file.
2466 if (mode == MT_SPEW_SECTION) {
2467 if (TheMessage->cm_format_type != FMT_RFC822) {
2469 cprintf("%d This is not a MIME message.\n",
2470 ERROR + ILLEGAL_VALUE);
2472 /* Parse the message text component */
2475 mptr = TheMessage->cm_fields[eMesageText];
2476 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2477 /* If section wasn't found, print an error
2480 if (do_proto) cprintf(
2481 "%d Section %s not found.\n",
2482 ERROR + FILE_NOT_FOUND,
2483 CCC->download_desired_section);
2486 return((CCC->download_fp != NULL) ? om_ok : om_mime_error);
2489 /* now for the user-mode message reading loops */
2490 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2492 /* Does the caller want to skip the headers? */
2493 if (headers_only == HEADERS_NONE) goto START_TEXT;
2495 /* Tell the client which format type we're using. */
2496 if ( (mode == MT_CITADEL) && (do_proto) ) {
2497 cprintf("type=%d\n", TheMessage->cm_format_type);
2500 /* nhdr=yes means that we're only displaying headers, no body */
2501 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2502 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2505 cprintf("nhdr=yes\n");
2508 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2509 OutputCtdlMsgHeaders(TheMessage, do_proto);
2512 /* begin header processing loop for RFC822 transfer format */
2516 strcpy(snode, NODENAME);
2517 if (mode == MT_RFC822)
2518 OutputRFC822MsgHeaders(
2523 suser, sizeof(suser),
2524 luser, sizeof(luser),
2525 fuser, sizeof(fuser),
2526 snode, sizeof(snode)
2530 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2531 suser[i] = tolower(suser[i]);
2532 if (!isalnum(suser[i])) suser[i]='_';
2535 if (mode == MT_RFC822) {
2536 if (!strcasecmp(snode, NODENAME)) {
2537 safestrncpy(snode, FQDN, sizeof snode);
2540 /* Construct a fun message id */
2541 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2542 if (strchr(mid, '@')==NULL) {
2543 cprintf("@%s", snode);
2547 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2548 cprintf("From: \"----\" <x@x.org>%s", nl);
2550 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2551 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2553 else if (!IsEmptyStr(fuser)) {
2554 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2557 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2560 /* Blank line signifying RFC822 end-of-headers */
2561 if (TheMessage->cm_format_type != FMT_RFC822) {
2566 /* end header processing loop ... at this point, we're in the text */
2568 if (headers_only == HEADERS_FAST) goto DONE;
2570 /* Tell the client about the MIME parts in this message */
2571 if (TheMessage->cm_format_type == FMT_RFC822) {
2572 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2573 mptr = TheMessage->cm_fields[eMesageText];
2574 memset(&ma, 0, sizeof(struct ma_info));
2575 mime_parser(mptr, NULL,
2576 (do_proto ? *list_this_part : NULL),
2577 (do_proto ? *list_this_pref : NULL),
2578 (do_proto ? *list_this_suff : NULL),
2581 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2582 Dump_RFC822HeadersBody(
2591 if (headers_only == HEADERS_ONLY) {
2595 /* signify start of msg text */
2596 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2597 if (do_proto) cprintf("text\n");
2600 if (TheMessage->cm_format_type == FMT_FIXED)
2603 mode, /* how would you like that message? */
2606 /* If the message on disk is format 0 (Citadel vari-format), we
2607 * output using the formatter at 80 columns. This is the final output
2608 * form if the transfer format is RFC822, but if the transfer format
2609 * is Citadel proprietary, it'll still work, because the indentation
2610 * for new paragraphs is correct and the client will reformat the
2611 * message to the reader's screen width.
2613 if (TheMessage->cm_format_type == FMT_CITADEL) {
2614 mptr = TheMessage->cm_fields[eMesageText];
2616 if (mode == MT_MIME) {
2617 cprintf("Content-type: text/x-citadel-variformat\n\n");
2622 /* If the message on disk is format 4 (MIME), we've gotta hand it
2623 * off to the MIME parser. The client has already been told that
2624 * this message is format 1 (fixed format), so the callback function
2625 * we use will display those parts as-is.
2627 if (TheMessage->cm_format_type == FMT_RFC822) {
2628 memset(&ma, 0, sizeof(struct ma_info));
2630 if (mode == MT_MIME) {
2631 ma.use_fo_hooks = 0;
2632 strcpy(ma.chosen_part, "1");
2633 ma.chosen_pref = 9999;
2634 ma.dont_decode = CCC->msg4_dont_decode;
2635 mime_parser(mptr, NULL,
2636 *choose_preferred, *fixed_output_pre,
2637 *fixed_output_post, (void *)&ma, 1);
2638 mime_parser(mptr, NULL,
2639 *output_preferred, NULL, NULL, (void *)&ma, 1);
2642 ma.use_fo_hooks = 1;
2643 mime_parser(mptr, NULL,
2644 *fixed_output, *fixed_output_pre,
2645 *fixed_output_post, (void *)&ma, 0);
2650 DONE: /* now we're done */
2651 if (do_proto) cprintf("000\n");
2657 * display a message (mode 0 - Citadel proprietary)
2659 void cmd_msg0(char *cmdbuf)
2662 int headers_only = HEADERS_ALL;
2664 msgid = extract_long(cmdbuf, 0);
2665 headers_only = extract_int(cmdbuf, 1);
2667 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0, NULL, NULL);
2673 * display a message (mode 2 - RFC822)
2675 void cmd_msg2(char *cmdbuf)
2678 int headers_only = HEADERS_ALL;
2680 msgid = extract_long(cmdbuf, 0);
2681 headers_only = extract_int(cmdbuf, 1);
2683 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0, NULL, NULL);
2689 * display a message (mode 3 - IGnet raw format - internal programs only)
2691 void cmd_msg3(char *cmdbuf)
2694 struct CtdlMessage *msg = NULL;
2697 if (CC->internal_pgm == 0) {
2698 cprintf("%d This command is for internal programs only.\n",
2699 ERROR + HIGHER_ACCESS_REQUIRED);
2703 msgnum = extract_long(cmdbuf, 0);
2704 msg = CtdlFetchMessage(msgnum, 1);
2706 cprintf("%d Message %ld not found.\n",
2707 ERROR + MESSAGE_NOT_FOUND, msgnum);
2711 serialize_message(&smr, msg);
2712 CtdlFreeMessage(msg);
2715 cprintf("%d Unable to serialize message\n",
2716 ERROR + INTERNAL_ERROR);
2720 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2721 client_write((char *)smr.ser, (int)smr.len);
2728 * Display a message using MIME content types
2730 void cmd_msg4(char *cmdbuf)
2735 msgid = extract_long(cmdbuf, 0);
2736 extract_token(section, cmdbuf, 1, '|', sizeof section);
2737 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0, NULL, NULL);
2743 * Client tells us its preferred message format(s)
2745 void cmd_msgp(char *cmdbuf)
2747 if (!strcasecmp(cmdbuf, "dont_decode")) {
2748 CC->msg4_dont_decode = 1;
2749 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2752 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2753 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2759 * Open a component of a MIME message as a download file
2761 void cmd_opna(char *cmdbuf)
2764 char desired_section[128];
2766 msgid = extract_long(cmdbuf, 0);
2767 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2768 safestrncpy(CC->download_desired_section, desired_section,
2769 sizeof CC->download_desired_section);
2770 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0, NULL, NULL);
2775 * Open a component of a MIME message and transmit it all at once
2777 void cmd_dlat(char *cmdbuf)
2780 char desired_section[128];
2782 msgid = extract_long(cmdbuf, 0);
2783 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2784 safestrncpy(CC->download_desired_section, desired_section,
2785 sizeof CC->download_desired_section);
2786 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0, NULL, NULL);
2791 * Save one or more message pointers into a specified room
2792 * (Returns 0 for success, nonzero for failure)
2793 * roomname may be NULL to use the current room
2795 * Note that the 'supplied_msg' field may be set to NULL, in which case
2796 * the message will be fetched from disk, by number, if we need to perform
2797 * replication checks. This adds an additional database read, so if the
2798 * caller already has the message in memory then it should be supplied. (Obviously
2799 * this mode of operation only works if we're saving a single message.)
2801 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2802 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2804 struct CitContext *CCC = CC;
2806 char hold_rm[ROOMNAMELEN];
2807 struct cdbdata *cdbfr;
2810 long highest_msg = 0L;
2813 struct CtdlMessage *msg = NULL;
2815 long *msgs_to_be_merged = NULL;
2816 int num_msgs_to_be_merged = 0;
2818 MSG_syslog(LOG_DEBUG,
2819 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2820 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2823 strcpy(hold_rm, CCC->room.QRname);
2826 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2827 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2828 if (num_newmsgs > 1) supplied_msg = NULL;
2830 /* Now the regular stuff */
2831 if (CtdlGetRoomLock(&CCC->room,
2832 ((roomname != NULL) ? roomname : CCC->room.QRname) )
2834 MSG_syslog(LOG_ERR, "No such room <%s>\n", roomname);
2835 return(ERROR + ROOM_NOT_FOUND);
2839 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2840 num_msgs_to_be_merged = 0;
2843 cdbfr = cdb_fetch(CDB_MSGLISTS, &CCC->room.QRnumber, sizeof(long));
2844 if (cdbfr == NULL) {
2848 msglist = (long *) cdbfr->ptr;
2849 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2850 num_msgs = cdbfr->len / sizeof(long);
2855 /* Create a list of msgid's which were supplied by the caller, but do
2856 * not already exist in the target room. It is absolutely taboo to
2857 * have more than one reference to the same message in a room.
2859 for (i=0; i<num_newmsgs; ++i) {
2861 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2862 if (msglist[j] == newmsgidlist[i]) {
2867 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2871 MSG_syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2874 * Now merge the new messages
2876 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2877 if (msglist == NULL) {
2878 MSGM_syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2879 free(msgs_to_be_merged);
2880 return (ERROR + INTERNAL_ERROR);
2882 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2883 num_msgs += num_msgs_to_be_merged;
2885 /* Sort the message list, so all the msgid's are in order */
2886 num_msgs = sort_msglist(msglist, num_msgs);
2888 /* Determine the highest message number */
2889 highest_msg = msglist[num_msgs - 1];
2891 /* Write it back to disk. */
2892 cdb_store(CDB_MSGLISTS, &CCC->room.QRnumber, (int)sizeof(long),
2893 msglist, (int)(num_msgs * sizeof(long)));
2895 /* Free up the memory we used. */
2898 /* Update the highest-message pointer and unlock the room. */
2899 CCC->room.QRhighest = highest_msg;
2900 CtdlPutRoomLock(&CCC->room);
2902 /* Perform replication checks if necessary */
2903 if ( (DoesThisRoomNeedEuidIndexing(&CCC->room)) && (do_repl_check) ) {
2904 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2906 for (i=0; i<num_msgs_to_be_merged; ++i) {
2907 msgid = msgs_to_be_merged[i];
2909 if (supplied_msg != NULL) {
2913 msg = CtdlFetchMessage(msgid, 0);
2917 ReplicationChecks(msg);
2919 /* If the message has an Exclusive ID, index that... */
2920 if (msg->cm_fields[eExclusiveID] != NULL) {
2921 index_message_by_euid(msg->cm_fields[eExclusiveID], &CCC->room, msgid);
2924 /* Free up the memory we may have allocated */
2925 if (msg != supplied_msg) {
2926 CtdlFreeMessage(msg);
2934 MSGM_syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2937 /* Submit this room for processing by hooks */
2938 PerformRoomHooks(&CCC->room);
2940 /* Go back to the room we were in before we wandered here... */
2941 CtdlGetRoom(&CCC->room, hold_rm);
2943 /* Bump the reference count for all messages which were merged */
2944 if (!suppress_refcount_adj) {
2945 AdjRefCountList(msgs_to_be_merged, num_msgs_to_be_merged, +1);
2948 /* Free up memory... */
2949 if (msgs_to_be_merged != NULL) {
2950 free(msgs_to_be_merged);
2953 /* Return success. */
2959 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2962 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2963 int do_repl_check, struct CtdlMessage *supplied_msg)
2965 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2972 * Message base operation to save a new message to the message store
2973 * (returns new message number)
2975 * This is the back end for CtdlSubmitMsg() and should not be directly
2976 * called by server-side modules.
2979 long send_message(struct CtdlMessage *msg) {
2980 struct CitContext *CCC = CC;
2988 /* Get a new message number */
2989 newmsgid = get_new_message_number();
2990 snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2991 (long unsigned int) time(NULL),
2992 (long unsigned int) newmsgid,
2996 /* Generate an ID if we don't have one already */
2997 if (msg->cm_fields[emessageId]==NULL) {
2998 msg->cm_fields[emessageId] = strdup(msgidbuf);
3001 /* If the message is big, set its body aside for storage elsewhere */
3002 if (msg->cm_fields[eMesageText] != NULL) {
3003 if (strlen(msg->cm_fields[eMesageText]) > BIGMSG) {
3005 holdM = msg->cm_fields[eMesageText];
3006 msg->cm_fields[eMesageText] = NULL;
3010 /* Serialize our data structure for storage in the database */
3011 serialize_message(&smr, msg);
3014 msg->cm_fields[eMesageText] = holdM;
3018 cprintf("%d Unable to serialize message\n",
3019 ERROR + INTERNAL_ERROR);
3023 /* Write our little bundle of joy into the message base */
3024 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
3025 smr.ser, smr.len) < 0) {
3026 MSGM_syslog(LOG_ERR, "Can't store message\n");
3030 cdb_store(CDB_BIGMSGS,
3040 /* Free the memory we used for the serialized message */
3043 /* Return the *local* message ID to the caller
3044 * (even if we're storing an incoming network message)
3052 * Serialize a struct CtdlMessage into the format used on disk and network.
3054 * This function loads up a "struct ser_ret" (defined in server.h) which
3055 * contains the length of the serialized message and a pointer to the
3056 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
3058 void serialize_message(struct ser_ret *ret, /* return values */
3059 struct CtdlMessage *msg) /* unserialized msg */
3061 struct CitContext *CCC = CC;
3062 size_t wlen, fieldlen;
3064 long lengths[NDiskFields];
3066 memset(lengths, 0, sizeof(lengths));
3069 * Check for valid message format
3071 if (is_valid_message(msg) == 0) {
3072 MSGM_syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
3079 for (i=0; i < NDiskFields; ++i)
3080 if (msg->cm_fields[FieldOrder[i]] != NULL)
3082 lengths[i] = strlen(msg->cm_fields[FieldOrder[i]]);
3083 ret->len += lengths[i] + 2;
3086 ret->ser = malloc(ret->len);
3087 if (ret->ser == NULL) {
3088 MSG_syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
3089 (long)ret->len, strerror(errno));
3096 ret->ser[1] = msg->cm_anon_type;
3097 ret->ser[2] = msg->cm_format_type;
3100 for (i=0; i < NDiskFields; ++i)
3101 if (msg->cm_fields[FieldOrder[i]] != NULL)
3103 fieldlen = lengths[i];
3104 ret->ser[wlen++] = (char)FieldOrder[i];
3106 memcpy(&ret->ser[wlen],
3107 msg->cm_fields[FieldOrder[i]],
3110 wlen = wlen + fieldlen + 1;
3113 if (ret->len != wlen) {
3114 MSG_syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
3115 (long)ret->len, (long)wlen);
3123 * Check to see if any messages already exist in the current room which
3124 * carry the same Exclusive ID as this one. If any are found, delete them.
3126 void ReplicationChecks(struct CtdlMessage *msg) {
3127 struct CitContext *CCC = CC;
3128 long old_msgnum = (-1L);
3130 if (DoesThisRoomNeedEuidIndexing(&CCC->room) == 0) return;
3132 MSG_syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
3135 /* No exclusive id? Don't do anything. */
3136 if (msg == NULL) return;
3137 if (msg->cm_fields[eExclusiveID] == NULL) return;
3138 if (IsEmptyStr(msg->cm_fields[eExclusiveID])) return;
3139 /*MSG_syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
3140 msg->cm_fields[eExclusiveID], CCC->room.QRname);*/
3142 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields[eExclusiveID], &CCC->room);
3143 if (old_msgnum > 0L) {
3144 MSG_syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
3145 CtdlDeleteMessages(CCC->room.QRname, &old_msgnum, 1, "");
3152 * Save a message to disk and submit it into the delivery system.
3154 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
3155 struct recptypes *recps, /* recipients (if mail) */
3156 const char *force, /* force a particular room? */
3157 int flags /* should the message be exported clean? */
3160 char submit_filename[128];
3161 char generated_timestamp[32];
3162 char hold_rm[ROOMNAMELEN];
3163 char actual_rm[ROOMNAMELEN];
3164 char force_room[ROOMNAMELEN];
3165 char content_type[SIZ]; /* We have to learn this */
3166 char recipient[SIZ];
3169 const char *mptr = NULL;
3170 struct ctdluser userbuf;
3172 struct MetaData smi;
3173 FILE *network_fp = NULL;
3174 static int seqnum = 1;
3175 struct CtdlMessage *imsg = NULL;
3177 size_t instr_alloc = 0;
3179 char *hold_R, *hold_D;
3180 char *collected_addresses = NULL;
3181 struct addresses_to_be_filed *aptr = NULL;
3182 StrBuf *saved_rfc822_version = NULL;
3183 int qualified_for_journaling = 0;
3184 CitContext *CCC = MyContext();
3185 char bounce_to[1024] = "";
3188 MSGM_syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3189 if (is_valid_message(msg) == 0) return(-1); /* self check */
3191 /* If this message has no timestamp, we take the liberty of
3192 * giving it one, right now.
3194 if (msg->cm_fields[eTimestamp] == NULL) {
3195 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3196 msg->cm_fields[eTimestamp] = strdup(generated_timestamp);
3199 /* If this message has no path, we generate one.
3201 if (msg->cm_fields[eMessagePath] == NULL) {
3202 if (msg->cm_fields[eAuthor] != NULL) {
3203 msg->cm_fields[eMessagePath] = strdup(msg->cm_fields[eAuthor]);
3204 for (a=0; !IsEmptyStr(&msg->cm_fields[eMessagePath][a]); ++a) {
3205 if (isspace(msg->cm_fields[eMessagePath][a])) {
3206 msg->cm_fields[eMessagePath][a] = ' ';
3211 msg->cm_fields[eMessagePath] = strdup("unknown");
3215 if (force == NULL) {
3216 strcpy(force_room, "");
3219 strcpy(force_room, force);
3222 /* Learn about what's inside, because it's what's inside that counts */
3223 if (msg->cm_fields[eMesageText] == NULL) {
3224 MSGM_syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3228 switch (msg->cm_format_type) {
3230 strcpy(content_type, "text/x-citadel-variformat");
3233 strcpy(content_type, "text/plain");
3236 strcpy(content_type, "text/plain");
3237 mptr = bmstrcasestr(msg->cm_fields[eMesageText], "Content-type:");
3240 safestrncpy(content_type, &mptr[13], sizeof content_type);
3241 striplt(content_type);
3242 aptr = content_type;
3243 while (!IsEmptyStr(aptr)) {
3255 /* Goto the correct room */
3256 room = (recps) ? CCC->room.QRname : SENTITEMS;
3257 MSG_syslog(LOG_DEBUG, "Selected room %s\n", room);
3258 strcpy(hold_rm, CCC->room.QRname);
3259 strcpy(actual_rm, CCC->room.QRname);
3260 if (recps != NULL) {
3261 strcpy(actual_rm, SENTITEMS);
3264 /* If the user is a twit, move to the twit room for posting */
3266 if (CCC->user.axlevel == AxProbU) {
3267 strcpy(hold_rm, actual_rm);
3268 strcpy(actual_rm, config.c_twitroom);
3269 MSGM_syslog(LOG_DEBUG, "Diverting to twit room\n");
3273 /* ...or if this message is destined for Aide> then go there. */
3274 if (!IsEmptyStr(force_room)) {
3275 strcpy(actual_rm, force_room);
3278 MSG_syslog(LOG_INFO, "Final selection: %s (%s)\n", actual_rm, room);
3279 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3280 /* CtdlGetRoom(&CCC->room, actual_rm); */
3281 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3285 * If this message has no O (room) field, generate one.
3287 if (msg->cm_fields[eOriginalRoom] == NULL) {
3288 msg->cm_fields[eOriginalRoom] = strdup(CCC->room.QRname);
3291 /* Perform "before save" hooks (aborting if any return nonzero) */
3292 MSGM_syslog(LOG_DEBUG, "Performing before-save hooks\n");
3293 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3296 * If this message has an Exclusive ID, and the room is replication
3297 * checking enabled, then do replication checks.
3299 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3300 ReplicationChecks(msg);
3303 /* Save it to disk */
3304 MSGM_syslog(LOG_DEBUG, "Saving to disk\n");
3305 newmsgid = send_message(msg);
3306 if (newmsgid <= 0L) return(-5);
3308 /* Write a supplemental message info record. This doesn't have to
3309 * be a critical section because nobody else knows about this message
3312 MSGM_syslog(LOG_DEBUG, "Creating MetaData record\n");
3313 memset(&smi, 0, sizeof(struct MetaData));
3314 smi.meta_msgnum = newmsgid;
3315 smi.meta_refcount = 0;
3316 safestrncpy(smi.meta_content_type, content_type,
3317 sizeof smi.meta_content_type);
3320 * Measure how big this message will be when rendered as RFC822.
3321 * We do this for two reasons:
3322 * 1. We need the RFC822 length for the new metadata record, so the
3323 * POP and IMAP services don't have to calculate message lengths
3324 * while the user is waiting (multiplied by potentially hundreds
3325 * or thousands of messages).
3326 * 2. If journaling is enabled, we will need an RFC822 version of the
3327 * message to attach to the journalized copy.
3329 if (CCC->redirect_buffer != NULL) {
3330 MSGM_syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3333 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3334 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3335 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3336 saved_rfc822_version = CCC->redirect_buffer;
3337 CCC->redirect_buffer = NULL;
3341 /* Now figure out where to store the pointers */
3342 MSGM_syslog(LOG_DEBUG, "Storing pointers\n");
3344 /* If this is being done by the networker delivering a private
3345 * message, we want to BYPASS saving the sender's copy (because there
3346 * is no local sender; it would otherwise go to the Trashcan).
3348 if ((!CCC->internal_pgm) || (recps == NULL)) {
3349 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3350 MSGM_syslog(LOG_ERR, "ERROR saving message pointer!\n");
3351 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3355 /* For internet mail, drop a copy in the outbound queue room */
3356 if ((recps != NULL) && (recps->num_internet > 0)) {
3357 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3360 /* If other rooms are specified, drop them there too. */
3361 if ((recps != NULL) && (recps->num_room > 0))
3362 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3363 extract_token(recipient, recps->recp_room, i,
3364 '|', sizeof recipient);
3365 MSG_syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);///// xxxx
3366 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3369 /* Bump this user's messages posted counter. */
3370 MSGM_syslog(LOG_DEBUG, "Updating user\n");
3371 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3372 CCC->user.posted = CCC->user.posted + 1;
3373 CtdlPutUserLock(&CCC->user);
3375 /* Decide where bounces need to be delivered */
3376 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3377 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3379 else if (CCC->logged_in) {
3380 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3383 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields[eAuthor], msg->cm_fields[eNodeName]);
3386 /* If this is private, local mail, make a copy in the
3387 * recipient's mailbox and bump the reference count.
3389 if ((recps != NULL) && (recps->num_local > 0))
3390 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3391 extract_token(recipient, recps->recp_local, i,
3392 '|', sizeof recipient);
3393 MSG_syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3395 if (CtdlGetUser(&userbuf, recipient) == 0) {
3396 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3397 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3398 CtdlBumpNewMailCounter(userbuf.usernum);
3399 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3400 /* Generate a instruction message for the Funambol notification
3401 * server, in the same style as the SMTP queue
3404 instr = malloc(instr_alloc);
3405 snprintf(instr, instr_alloc,
3406 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3408 SPOOLMIME, newmsgid, (long)time(NULL),
3412 imsg = malloc(sizeof(struct CtdlMessage));
3413 memset(imsg, 0, sizeof(struct CtdlMessage));
3414 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3415 imsg->cm_anon_type = MES_NORMAL;
3416 imsg->cm_format_type = FMT_RFC822;
3417 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3418 imsg->cm_fields[eAuthor] = strdup("Citadel");
3419 imsg->cm_fields[eJournal] = strdup("do not journal");
3420 imsg->cm_fields[eMesageText] = instr; /* imsg owns this memory now */
3421 imsg->cm_fields[eExtnotify] = strdup(recipient);
3422 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3423 CtdlFreeMessage(imsg);
3427 MSG_syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3428 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3432 /* Perform "after save" hooks */
3433 MSGM_syslog(LOG_DEBUG, "Performing after-save hooks\n");
3434 if (msg->cm_fields[eVltMsgNum] != NULL) free(msg->cm_fields[eVltMsgNum]);
3435 msg->cm_fields[eVltMsgNum] = malloc(20);
3436 snprintf(msg->cm_fields[eVltMsgNum], 20, "%ld", newmsgid);
3437 PerformMessageHooks(msg, EVT_AFTERSAVE);
3438 free(msg->cm_fields[eVltMsgNum]);
3439 msg->cm_fields[eVltMsgNum] = NULL;
3441 /* For IGnet mail, we have to save a new copy into the spooler for
3442 * each recipient, with the R and D fields set to the recipient and
3443 * destination-node. This has two ugly side effects: all other
3444 * recipients end up being unlisted in this recipient's copy of the
3445 * message, and it has to deliver multiple messages to the same
3446 * node. We'll revisit this again in a year or so when everyone has
3447 * a network spool receiver that can handle the new style messages.
3449 if ((recps != NULL) && (recps->num_ignet > 0))
3450 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3451 extract_token(recipient, recps->recp_ignet, i,
3452 '|', sizeof recipient);
3454 hold_R = msg->cm_fields[eRecipient];
3455 hold_D = msg->cm_fields[eDestination];
3456 msg->cm_fields[eRecipient] = malloc(SIZ);
3457 msg->cm_fields[eDestination] = malloc(128);
3458 extract_token(msg->cm_fields[eRecipient], recipient, 0, '@', SIZ);
3459 extract_token(msg->cm_fields[eDestination], recipient, 1, '@', 128);
3461 serialize_message(&smr, msg);
3463 snprintf(submit_filename, sizeof submit_filename,
3464 "%s/netmail.%04lx.%04x.%04x",
3466 (long) getpid(), CCC->cs_pid, ++seqnum);
3467 network_fp = fopen(submit_filename, "wb+");
3468 if (network_fp != NULL) {
3469 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3471 MSG_syslog(LOG_EMERG, "CtdlSubmitMsg(): Couldn't write network spool file: %s\n",
3479 free(msg->cm_fields[eRecipient]);
3480 free(msg->cm_fields[eDestination]);
3481 msg->cm_fields[eRecipient] = hold_R;
3482 msg->cm_fields[eDestination] = hold_D;
3485 /* Go back to the room we started from */
3486 MSG_syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3487 if (strcasecmp(hold_rm, CCC->room.QRname))
3488 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3490 /* For internet mail, generate delivery instructions.
3491 * Yes, this is recursive. Deal with it. Infinite recursion does
3492 * not happen because the delivery instructions message does not
3493 * contain a recipient.
3495 if ((recps != NULL) && (recps->num_internet > 0)) {
3496 StrBuf *SpoolMsg = NewStrBuf();
3499 MSGM_syslog(LOG_DEBUG, "Generating delivery instructions\n");
3501 StrBufPrintf(SpoolMsg,
3502 "Content-type: "SPOOLMIME"\n"
3511 if (recps->envelope_from != NULL) {
3512 StrBufAppendBufPlain(SpoolMsg, HKEY("envelope_from|"), 0);
3513 StrBufAppendBufPlain(SpoolMsg, recps->envelope_from, -1, 0);
3514 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3516 if (recps->sending_room != NULL) {
3517 StrBufAppendBufPlain(SpoolMsg, HKEY("source_room|"), 0);
3518 StrBufAppendBufPlain(SpoolMsg, recps->sending_room, -1, 0);
3519 StrBufAppendBufPlain(SpoolMsg, HKEY("\n"), 0);
3522 nTokens = num_tokens(recps->recp_internet, '|');
3523 for (i = 0; i < nTokens; i++) {
3525 len = extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3527 StrBufAppendBufPlain(SpoolMsg, HKEY("remote|"), 0);
3528 StrBufAppendBufPlain(SpoolMsg, recipient, len, 0);
3529 StrBufAppendBufPlain(SpoolMsg, HKEY("|0||\n"), 0);
3533 imsg = malloc(sizeof(struct CtdlMessage));
3534 memset(imsg, 0, sizeof(struct CtdlMessage));
3535 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3536 imsg->cm_anon_type = MES_NORMAL;
3537 imsg->cm_format_type = FMT_RFC822;
3538 imsg->cm_fields[eMsgSubject] = strdup("QMSG");
3539 imsg->cm_fields[eAuthor] = strdup("Citadel");
3540 imsg->cm_fields[eJournal] = strdup("do not journal");
3541 imsg->cm_fields[eMesageText] = SmashStrBuf(&SpoolMsg); /* imsg owns this memory now */
3542 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3543 CtdlFreeMessage(imsg);
3547 * Any addresses to harvest for someone's address book?
3549 if ( (CCC->logged_in) && (recps != NULL) ) {
3550 collected_addresses = harvest_collected_addresses(msg);
3553 if (collected_addresses != NULL) {
3554 aptr = (struct addresses_to_be_filed *)
3555 malloc(sizeof(struct addresses_to_be_filed));
3556 CtdlMailboxName(actual_rm, sizeof actual_rm,
3557 &CCC->user, USERCONTACTSROOM);
3558 aptr->roomname = strdup(actual_rm);
3559 aptr->collected_addresses = collected_addresses;
3560 begin_critical_section(S_ATBF);
3563 end_critical_section(S_ATBF);
3567 * Determine whether this message qualifies for journaling.
3569 if (msg->cm_fields[eJournal] != NULL) {
3570 qualified_for_journaling = 0;
3573 if (recps == NULL) {
3574 qualified_for_journaling = config.c_journal_pubmsgs;
3576 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3577 qualified_for_journaling = config.c_journal_email;
3580 qualified_for_journaling = config.c_journal_pubmsgs;
3585 * Do we have to perform journaling? If so, hand off the saved
3586 * RFC822 version will be handed off to the journaler for background
3587 * submit. Otherwise, we have to free the memory ourselves.
3589 if (saved_rfc822_version != NULL) {
3590 if (qualified_for_journaling) {
3591 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3594 FreeStrBuf(&saved_rfc822_version);
3604 * Convenience function for generating small administrative messages.
3606 void quickie_message(const char *from,
3607 const char *fromaddr,
3612 const char *subject)
3614 struct CtdlMessage *msg;
3615 struct recptypes *recp = NULL;
3617 msg = malloc(sizeof(struct CtdlMessage));
3618 memset(msg, 0, sizeof(struct CtdlMessage));
3619 msg->cm_magic = CTDLMESSAGE_MAGIC;
3620 msg->cm_anon_type = MES_NORMAL;
3621 msg->cm_format_type = format_type;
3624 msg->cm_fields[eAuthor] = strdup(from);
3626 else if (fromaddr != NULL) {
3627 msg->cm_fields[eAuthor] = strdup(fromaddr);
3628 if (strchr(msg->cm_fields[eAuthor], '@')) {
3629 *strchr(msg->cm_fields[eAuthor], '@') = 0;
3633 msg->cm_fields[eAuthor] = strdup("Citadel");
3636 if (fromaddr != NULL) msg->cm_fields[erFc822Addr] = strdup(fromaddr);
3637 if (room != NULL) msg->cm_fields[eOriginalRoom] = strdup(room);
3638 msg->cm_fields[eNodeName] = strdup(NODENAME);
3640 msg->cm_fields[eRecipient] = strdup(to);
3641 recp = validate_recipients(to, NULL, 0);
3643 if (subject != NULL) {
3644 msg->cm_fields[eMsgSubject] = strdup(subject);
3646 msg->cm_fields[eMesageText] = strdup(text);
3648 CtdlSubmitMsg(msg, recp, room, 0);
3649 CtdlFreeMessage(msg);
3650 if (recp != NULL) free_recipients(recp);
3653 void flood_protect_quickie_message(const char *from,
3654 const char *fromaddr,
3659 const char *subject,
3661 const char **CritStr,
3668 u_char rawdigest[MD5_DIGEST_LEN];
3669 struct MD5Context md5context;
3673 time_t tsday = NOW / (8*60*60); /* just care for a day... */
3675 tslen = snprintf(timestamp, sizeof(timestamp), "%ld", tsday);
3676 MD5Init(&md5context);
3678 for (i = 0; i < nCriterions; i++)
3679 MD5Update(&md5context,
3680 (const unsigned char*)CritStr[i], CritStrLen[i]);
3681 MD5Update(&md5context,
3682 (const unsigned char*)timestamp, tslen);
3683 MD5Final(rawdigest, &md5context);
3685 guid = NewStrBufPlain(NULL,
3686 MD5_DIGEST_LEN * 2 + 12);
3687 StrBufHexEscAppend(guid, NULL, rawdigest, MD5_DIGEST_LEN);
3688 StrBufAppendBufPlain(guid, HKEY("_fldpt"), 0);
3689 if (StrLength(guid) > 40)
3690 StrBufCutAt(guid, 40, NULL);
3692 if (CheckIfAlreadySeen("FPAideMessage",
3701 /* yes, we did. flood protection kicks in. */
3703 "not sending message again\n");
3707 /* no, this message isn't sent recently; go ahead. */
3708 quickie_message(from,
3719 * Back end function used by CtdlMakeMessage() and similar functions
3721 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3723 size_t maxlen, /* maximum message length */
3724 StrBuf *exist, /* if non-null, append to it;
3725 exist is ALWAYS freed */
3726 int crlf, /* CRLF newlines instead of LF */
3727 int *sock /* socket handle or 0 for this session's client socket */
3736 LineBuf = NewStrBufPlain(NULL, SIZ);
3737 if (exist == NULL) {
3738 Message = NewStrBufPlain(NULL, 4 * SIZ);
3741 Message = NewStrBufDup(exist);
3744 /* Do we need to change leading ".." to "." for SMTP escaping? */
3745 if ((tlen == 1) && (*terminator == '.')) {
3749 /* read in the lines of message text one by one */
3752 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3757 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3759 if ((StrLength(LineBuf) == tlen) &&
3760 (!strcmp(ChrPtr(LineBuf), terminator)))
3763 if ( (!flushing) && (!finished) ) {
3765 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3768 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3771 /* Unescape SMTP-style input of two dots at the beginning of the line */
3773 (StrLength(LineBuf) == 2) &&
3774 (!strcmp(ChrPtr(LineBuf), "..")))
3776 StrBufCutLeft(LineBuf, 1);
3779 StrBufAppendBuf(Message, LineBuf, 0);
3782 /* if we've hit the max msg length, flush the rest */
3783 if (StrLength(Message) >= maxlen) flushing = 1;
3785 } while (!finished);
3786 FreeStrBuf(&LineBuf);
3790 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3794 FreeStrBuf(&(*Msg)->MsgBuf);
3800 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3802 size_t maxlen, /* maximum message length */
3803 size_t expectlen, /* if we expect a message, how long should it be? */
3804 StrBuf *exist, /* if non-null, append to it;
3805 exist is ALWAYS freed */
3806 long eLen, /* length of exist */
3807 int crlf /* CRLF newlines instead of LF */
3810 ReadAsyncMsg *NewMsg;
3812 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3813 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3815 if (exist == NULL) {
3818 if (expectlen == 0) {
3822 len = expectlen + 10;
3824 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3827 NewMsg->MsgBuf = NewStrBufDup(exist);
3829 /* Do we need to change leading ".." to "." for SMTP escaping? */
3830 if ((tlen == 1) && (*terminator == '.')) {
3834 NewMsg->terminator = terminator;
3835 NewMsg->tlen = tlen;
3837 NewMsg->maxlen = maxlen;
3839 NewMsg->crlf = crlf;
3845 * Back end function used by CtdlMakeMessage() and similar functions
3847 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3849 ReadAsyncMsg *ReadMsg;
3850 int MsgFinished = 0;
3851 eReadState Finished = eMustReadMore;
3856 const char *pch = ChrPtr(IO->SendBuf.Buf);
3857 const char *pchh = IO->SendBuf.ReadWritePointer;
3863 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3864 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3865 ((CitContext*)(IO->CitContext))->ServiceName,
3868 fd = fopen(fn, "a+");
3871 ReadMsg = IO->ReadMsg;
3873 /* read in the lines of message text one by one */
3875 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3878 case eMustReadMore: /// read new from socket...
3880 if (IO->RecvBuf.ReadWritePointer != NULL) {
3881 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3882 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3884 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3888 fprintf(fd, "BufferEmpty! \n");
3894 case eBufferNotEmpty: /* shouldn't happen... */
3895 case eReadSuccess: /// done for now...
3897 case eReadFail: /// WHUT?
3903 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3904 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3907 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3910 else if (!ReadMsg->flushing) {
3913 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3916 /* Unescape SMTP-style input of two dots at the beginning of the line */
3917 if ((ReadMsg->dodot) &&
3918 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3919 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3922 fprintf(fd, "UnEscaped!\n");
3924 StrBufCutLeft(IO->IOBuf, 1);
3927 if (ReadMsg->crlf) {
3928 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3931 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3934 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3937 /* if we've hit the max msg length, flush the rest */
3938 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3940 } while (!MsgFinished);
3943 fprintf(fd, "Done with reading; %s.\n, ",
3944 (MsgFinished)?"Message Finished": "FAILED");
3948 return eReadSuccess;
3955 * Back end function used by CtdlMakeMessage() and similar functions
3957 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3959 size_t maxlen, /* maximum message length */
3960 StrBuf *exist, /* if non-null, append to it;
3961 exist is ALWAYS freed */
3962 int crlf, /* CRLF newlines instead of LF */
3963 int *sock /* socket handle or 0 for this session's client socket */
3968 Message = CtdlReadMessageBodyBuf(terminator,
3974 if (Message == NULL)
3977 return SmashStrBuf(&Message);
3982 * Build a binary message to be saved on disk.
3983 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3984 * will become part of the message. This means you are no longer
3985 * responsible for managing that memory -- it will be freed along with
3986 * the rest of the fields when CtdlFreeMessage() is called.)
3989 struct CtdlMessage *CtdlMakeMessage(
3990 struct ctdluser *author, /* author's user structure */
3991 char *recipient, /* NULL if it's not mail */
3992 char *recp_cc, /* NULL if it's not mail */
3993 char *room, /* room where it's going */
3994 int type, /* see MES_ types in header file */
3995 int format_type, /* variformat, plain text, MIME... */
3996 char *fake_name, /* who we're masquerading as */
3997 char *my_email, /* which of my email addresses to use (empty is ok) */
3998 char *subject, /* Subject (optional) */
3999 char *supplied_euid, /* ...or NULL if this is irrelevant */
4000 char *preformatted_text, /* ...or NULL to read text from client */
4001 char *references /* Thread references */
4003 char dest_node[256];
4005 struct CtdlMessage *msg;
4007 StrBuf *FakeEncAuthor = NULL;
4009 msg = malloc(sizeof(struct CtdlMessage));
4010 memset(msg, 0, sizeof(struct CtdlMessage));
4011 msg->cm_magic = CTDLMESSAGE_MAGIC;
4012 msg->cm_anon_type = type;
4013 msg->cm_format_type = format_type;
4015 /* Don't confuse the poor folks if it's not routed mail. */
4016 strcpy(dest_node, "");
4018 if (recipient != NULL) striplt(recipient);
4019 if (recp_cc != NULL) striplt(recp_cc);
4021 /* Path or Return-Path */
4022 if (my_email == NULL) my_email = "";
4024 if (!IsEmptyStr(my_email)) {
4025 msg->cm_fields[eMessagePath] = strdup(my_email);
4028 snprintf(buf, sizeof buf, "%s", author->fullname);
4029 msg->cm_fields[eMessagePath] = strdup(buf);
4031 convert_spaces_to_underscores(msg->cm_fields[eMessagePath]);
4033 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
4034 msg->cm_fields[eTimestamp] = strdup(buf);
4036 if ((fake_name != NULL) && (fake_name[0])) { /* author */
4037 FakeAuthor = NewStrBufPlain (fake_name, -1);
4040 FakeAuthor = NewStrBufPlain (author->fullname, -1);
4042 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
4043 msg->cm_fields[eAuthor] = SmashStrBuf(&FakeEncAuthor);
4044 FreeStrBuf(&FakeAuthor);
4046 if (CC->room.QRflags & QR_MAILBOX) { /* room */
4047 msg->cm_fields[eOriginalRoom] = strdup(&CC->room.QRname[11]);
4050 msg->cm_fields[eOriginalRoom] = strdup(CC->room.QRname);
4053 msg->cm_fields[eNodeName] = strdup(NODENAME); /* nodename */
4054 msg->cm_fields[eHumanNode] = strdup(HUMANNODE); /* hnodename */
4056 if ((recipient != NULL) && (recipient[0] != 0)) {
4057 msg->cm_fields[eRecipient] = strdup(recipient);
4059 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
4060 msg->cm_fields[eCarbonCopY] = strdup(recp_cc);
4062 if (dest_node[0] != 0) {
4063 msg->cm_fields[eDestination] = strdup(dest_node);
4066 if (!IsEmptyStr(my_email)) {
4067 msg->cm_fields[erFc822Addr] = strdup(my_email);
4069 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
4070 msg->cm_fields[erFc822Addr] = strdup(CC->cs_inet_email);
4073 if (subject != NULL) {
4076 length = strlen(subject);
4082 while ((subject[i] != '\0') &&
4083 (IsAscii = isascii(subject[i]) != 0 ))
4086 msg->cm_fields[eMsgSubject] = strdup(subject);
4087 else /* ok, we've got utf8 in the string. */
4089 msg->cm_fields[eMsgSubject] = rfc2047encode(subject, length);
4095 if (supplied_euid != NULL) {
4096 msg->cm_fields[eExclusiveID] = strdup(supplied_euid);
4099 if ((references != NULL) && (!IsEmptyStr(references))) {
4100 if (msg->cm_fields[eWeferences] != NULL)
4101 free(msg->cm_fields[eWeferences]);
4102 msg->cm_fields[eWeferences] = strdup(references);
4105 if (preformatted_text != NULL) {
4106 msg->cm_fields[eMesageText] = preformatted_text;
4109 msg->cm_fields[eMesageText] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
4116 * Check to see whether we have permission to post a message in the current
4117 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
4118 * returns 0 on success.
4120 int CtdlDoIHavePermissionToPostInThisRoom(
4123 const char* RemoteIdentifier,
4129 if (!(CC->logged_in) &&
4130 (PostPublic == POST_LOGGED_IN)) {
4131 snprintf(errmsgbuf, n, "Not logged in.");
4132 return (ERROR + NOT_LOGGED_IN);
4134 else if (PostPublic == CHECK_EXISTANCE) {
4135 return (0); // We're Evaling whether a recipient exists
4137 else if (!(CC->logged_in)) {
4139 if ((CC->room.QRflags & QR_READONLY)) {
4140 snprintf(errmsgbuf, n, "Not logged in.");
4141 return (ERROR + NOT_LOGGED_IN);
4143 if (CC->room.QRflags2 & QR2_MODERATED) {
4144 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
4145 return (ERROR + NOT_LOGGED_IN);
4147 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
4149 return CtdlNetconfigCheckRoomaccess(errmsgbuf, n, RemoteIdentifier);
4155 if ((CC->user.axlevel < AxProbU)
4156 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
4157 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
4158 return (ERROR + HIGHER_ACCESS_REQUIRED);
4161 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4163 if (ra & UA_POSTALLOWED) {
4164 strcpy(errmsgbuf, "OK to post or reply here");
4168 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
4170 * To be thorough, we ought to check to see if the message they are
4171 * replying to is actually a valid one in this room, but unless this
4172 * actually becomes a problem we'll go with high performance instead.
4174 strcpy(errmsgbuf, "OK to reply here");
4178 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
4179 /* Clarify what happened with a better error message */
4180 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
4181 return (ERROR + HIGHER_ACCESS_REQUIRED);
4184 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
4185 return (ERROR + HIGHER_ACCESS_REQUIRED);
4191 * Check to see if the specified user has Internet mail permission
4192 * (returns nonzero if permission is granted)
4194 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
4196 /* Do not allow twits to send Internet mail */
4197 if (who->axlevel <= AxProbU) return(0);
4199 /* Globally enabled? */
4200 if (config.c_restrict == 0) return(1);
4202 /* User flagged ok? */
4203 if (who->flags & US_INTERNET) return(2);
4205 /* Admin level access? */
4206 if (who->axlevel >= AxAideU) return(3);
4208 /* No mail for you! */
4214 * Validate recipients, count delivery types and errors, and handle aliasing
4215 * FIXME check for dupes!!!!!
4217 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4218 * were specified, or the number of addresses found invalid.
4220 * Caller needs to free the result using free_recipients()
4222 struct recptypes *validate_recipients(const char *supplied_recipients,
4223 const char *RemoteIdentifier,
4225 struct CitContext *CCC = CC;
4226 struct recptypes *ret;
4227 char *recipients = NULL;
4229 char this_recp[256];
4230 char this_recp_cooked[256];
4237 struct ctdluser tempUS;
4238 struct ctdlroom tempQR;
4239 struct ctdlroom tempQR2;
4245 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4246 if (ret == NULL) return(NULL);
4248 /* Set all strings to null and numeric values to zero */
4249 memset(ret, 0, sizeof(struct recptypes));
4251 if (supplied_recipients == NULL) {
4252 recipients = strdup("");
4255 recipients = strdup(supplied_recipients);
4258 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4259 * actually need, but it's healthier for the heap than doing lots of tiny
4260 * realloc() calls instead.
4262 len = strlen(recipients) + 1024;
4263 ret->errormsg = malloc(len);
4264 ret->recp_local = malloc(len);
4265 ret->recp_internet = malloc(len);
4266 ret->recp_ignet = malloc(len);
4267 ret->recp_room = malloc(len);
4268 ret->display_recp = malloc(len);
4269 ret->recp_orgroom = malloc(len);
4270 org_recp = malloc(len);
4272 ret->errormsg[0] = 0;
4273 ret->recp_local[0] = 0;
4274 ret->recp_internet[0] = 0;
4275 ret->recp_ignet[0] = 0;
4276 ret->recp_room[0] = 0;
4277 ret->recp_orgroom[0] = 0;
4278 ret->display_recp[0] = 0;
4280 ret->recptypes_magic = RECPTYPES_MAGIC;
4282 /* Change all valid separator characters to commas */
4283 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4284 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4285 recipients[i] = ',';
4289 /* Now start extracting recipients... */
4291 while (!IsEmptyStr(recipients)) {
4292 for (i=0; i<=strlen(recipients); ++i) {
4293 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4294 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4295 safestrncpy(this_recp, recipients, i+1);
4297 if (recipients[i] == ',') {
4298 strcpy(recipients, &recipients[i+1]);
4301 strcpy(recipients, "");
4308 if (IsEmptyStr(this_recp))
4310 MSG_syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4313 strcpy(org_recp, this_recp);
4316 mailtype = alias(this_recp);
4318 for (j = 0; !IsEmptyStr(&this_recp[j]); ++j) {
4319 if (this_recp[j]=='_') {
4320 this_recp_cooked[j] = ' ';
4323 this_recp_cooked[j] = this_recp[j];
4326 this_recp_cooked[j] = '\0';
4331 if (!strcasecmp(this_recp, "sysop")) {
4333 strcpy(this_recp, config.c_aideroom);
4334 if (!IsEmptyStr(ret->recp_room)) {
4335 strcat(ret->recp_room, "|");
4337 strcat(ret->recp_room, this_recp);
4339 else if ( (!strncasecmp(this_recp, "room_", 5))
4340 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4342 /* Save room so we can restore it later */
4343 tempQR2 = CCC->room;
4346 /* Check permissions to send mail to this room */
4347 err = CtdlDoIHavePermissionToPostInThisRoom(
4352 0 /* 0 = not a reply */
4361 if (!IsEmptyStr(ret->recp_room)) {
4362 strcat(ret->recp_room, "|");
4364 strcat(ret->recp_room, &this_recp_cooked[5]);
4366 if (!IsEmptyStr(ret->recp_orgroom)) {
4367 strcat(ret->recp_orgroom, "|");
4369 strcat(ret->recp_orgroom, org_recp);
4373 /* Restore room in case something needs it */
4374 CCC->room = tempQR2;
4377 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4379 strcpy(this_recp, tempUS.fullname);
4380 if (!IsEmptyStr(ret->recp_local)) {
4381 strcat(ret->recp_local, "|");
4383 strcat(ret->recp_local, this_recp);
4385 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4387 strcpy(this_recp, tempUS.fullname);
4388 if (!IsEmptyStr(ret->recp_local)) {
4389 strcat(ret->recp_local, "|");
4391 strcat(ret->recp_local, this_recp);
4399 /* Yes, you're reading this correctly: if the target
4400 * domain points back to the local system or an attached
4401 * Citadel directory, the address is invalid. That's
4402 * because if the address were valid, we would have
4403 * already translated it to a local address by now.
4405 if (IsDirectory(this_recp, 0)) {
4410 ++ret->num_internet;
4411 if (!IsEmptyStr(ret->recp_internet)) {
4412 strcat(ret->recp_internet, "|");
4414 strcat(ret->recp_internet, this_recp);
4419 if (!IsEmptyStr(ret->recp_ignet)) {
4420 strcat(ret->recp_ignet, "|");
4422 strcat(ret->recp_ignet, this_recp);
4430 if (IsEmptyStr(errmsg)) {
4431 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4434 snprintf(append, sizeof append, "%s", errmsg);
4436 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4437 if (!IsEmptyStr(ret->errormsg)) {
4438 strcat(ret->errormsg, "; ");
4440 strcat(ret->errormsg, append);
4444 if (IsEmptyStr(ret->display_recp)) {
4445 strcpy(append, this_recp);
4448 snprintf(append, sizeof append, ", %s", this_recp);
4450 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4451 strcat(ret->display_recp, append);
4457 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4458 ret->num_room + ret->num_error) == 0) {
4459 ret->num_error = (-1);
4460 strcpy(ret->errormsg, "No recipients specified.");
4463 MSGM_syslog(LOG_DEBUG, "validate_recipients()\n");
4464 MSG_syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4465 MSG_syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4466 MSG_syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4467 MSG_syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4468 MSG_syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4476 * Destructor for struct recptypes
4478 void free_recipients(struct recptypes *valid) {
4480 if (valid == NULL) {
4484 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4485 struct CitContext *CCC = CC;
4486 MSGM_syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4490 if (valid->errormsg != NULL) free(valid->errormsg);
4491 if (valid->recp_local != NULL) free(valid->recp_local);
4492 if (valid->recp_internet != NULL) free(valid->recp_internet);
4493 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4494 if (valid->recp_room != NULL) free(valid->recp_room);
4495 if (valid->recp_orgroom != NULL) free(valid->recp_orgroom);
4496 if (valid->display_recp != NULL) free(valid->display_recp);
4497 if (valid->bounce_to != NULL) free(valid->bounce_to);
4498 if (valid->envelope_from != NULL) free(valid->envelope_from);
4499 if (valid->sending_room != NULL) free(valid->sending_room);
4506 * message entry - mode 0 (normal)
4508 void cmd_ent0(char *entargs)
4510 struct CitContext *CCC = CC;
4515 char supplied_euid[128];
4517 int format_type = 0;
4518 char newusername[256];
4519 char newuseremail[256];
4520 struct CtdlMessage *msg;
4524 struct recptypes *valid = NULL;
4525 struct recptypes *valid_to = NULL;
4526 struct recptypes *valid_cc = NULL;
4527 struct recptypes *valid_bcc = NULL;
4529 int subject_required = 0;
4534 int newuseremail_ok = 0;
4535 char references[SIZ];
4540 post = extract_int(entargs, 0);
4541 extract_token(recp, entargs, 1, '|', sizeof recp);
4542 anon_flag = extract_int(entargs, 2);
4543 format_type = extract_int(entargs, 3);
4544 extract_token(subject, entargs, 4, '|', sizeof subject);
4545 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4546 do_confirm = extract_int(entargs, 6);
4547 extract_token(cc, entargs, 7, '|', sizeof cc);
4548 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4549 switch(CC->room.QRdefaultview) {
4552 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4555 supplied_euid[0] = 0;
4558 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4559 extract_token(references, entargs, 11, '|', sizeof references);
4560 for (ptr=references; *ptr != 0; ++ptr) {
4561 if (*ptr == '!') *ptr = '|';
4564 /* first check to make sure the request is valid. */
4566 err = CtdlDoIHavePermissionToPostInThisRoom(
4571 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4575 cprintf("%d %s\n", err, errmsg);
4579 /* Check some other permission type things. */
4581 if (IsEmptyStr(newusername)) {
4582 strcpy(newusername, CCC->user.fullname);
4584 if ( (CCC->user.axlevel < AxAideU)
4585 && (strcasecmp(newusername, CCC->user.fullname))
4586 && (strcasecmp(newusername, CCC->cs_inet_fn))
4588 cprintf("%d You don't have permission to author messages as '%s'.\n",
4589 ERROR + HIGHER_ACCESS_REQUIRED,
4596 if (IsEmptyStr(newuseremail)) {
4597 newuseremail_ok = 1;
4600 if (!IsEmptyStr(newuseremail)) {
4601 if (!strcasecmp(newuseremail, CCC->cs_inet_email)) {
4602 newuseremail_ok = 1;
4604 else if (!IsEmptyStr(CCC->cs_inet_other_emails)) {
4605 j = num_tokens(CCC->cs_inet_other_emails, '|');
4606 for (i=0; i<j; ++i) {
4607 extract_token(buf, CCC->cs_inet_other_emails, i, '|', sizeof buf);
4608 if (!strcasecmp(newuseremail, buf)) {
4609 newuseremail_ok = 1;
4615 if (!newuseremail_ok) {
4616 cprintf("%d You don't have permission to author messages as '%s'.\n",
4617 ERROR + HIGHER_ACCESS_REQUIRED,
4623 CCC->cs_flags |= CS_POSTING;
4625 /* In mailbox rooms we have to behave a little differently --
4626 * make sure the user has specified at least one recipient. Then
4627 * validate the recipient(s). We do this for the Mail> room, as
4628 * well as any room which has the "Mailbox" view set - unless it
4629 * is the DRAFTS room which does not require recipients
4632 if ( ( ( (CCC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CCC->room.QRname[11], MAILROOM)) )
4633 || ( (CCC->room.QRflags & QR_MAILBOX) && (CCC->curr_view == VIEW_MAILBOX) )
4634 ) && (strcasecmp(&CCC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4635 if (CCC->user.axlevel < AxProbU) {
4636 strcpy(recp, "sysop");
4641 valid_to = validate_recipients(recp, NULL, 0);
4642 if (valid_to->num_error > 0) {
4643 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4644 free_recipients(valid_to);
4648 valid_cc = validate_recipients(cc, NULL, 0);
4649 if (valid_cc->num_error > 0) {
4650 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4651 free_recipients(valid_to);
4652 free_recipients(valid_cc);
4656 valid_bcc = validate_recipients(bcc, NULL, 0);
4657 if (valid_bcc->num_error > 0) {
4658 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4659 free_recipients(valid_to);
4660 free_recipients(valid_cc);
4661 free_recipients(valid_bcc);
4665 /* Recipient required, but none were specified */
4666 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4667 free_recipients(valid_to);
4668 free_recipients(valid_cc);
4669 free_recipients(valid_bcc);
4670 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4674 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4675 if (CtdlCheckInternetMailPermission(&CCC->user)==0) {
4676 cprintf("%d You do not have permission "
4677 "to send Internet mail.\n",
4678 ERROR + HIGHER_ACCESS_REQUIRED);
4679 free_recipients(valid_to);
4680 free_recipients(valid_cc);
4681 free_recipients(valid_bcc);
4686 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)
4687 && (CCC->user.axlevel < AxNetU) ) {
4688 cprintf("%d Higher access required for network mail.\n",
4689 ERROR + HIGHER_ACCESS_REQUIRED);
4690 free_recipients(valid_to);
4691 free_recipients(valid_cc);
4692 free_recipients(valid_bcc);
4696 if ((RESTRICT_INTERNET == 1)
4697 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4698 && ((CCC->user.flags & US_INTERNET) == 0)
4699 && (!CCC->internal_pgm)) {
4700 cprintf("%d You don't have access to Internet mail.\n",
4701 ERROR + HIGHER_ACCESS_REQUIRED);
4702 free_recipients(valid_to);
4703 free_recipients(valid_cc);
4704 free_recipients(valid_bcc);
4710 /* Is this a room which has anonymous-only or anonymous-option? */
4711 anonymous = MES_NORMAL;
4712 if (CCC->room.QRflags & QR_ANONONLY) {
4713 anonymous = MES_ANONONLY;
4715 if (CCC->room.QRflags & QR_ANONOPT) {
4716 if (anon_flag == 1) { /* only if the user requested it */
4717 anonymous = MES_ANONOPT;
4721 if ((CCC->room.QRflags & QR_MAILBOX) == 0) {
4725 /* Recommend to the client that the use of a message subject is
4726 * strongly recommended in this room, if either the SUBJECTREQ flag
4727 * is set, or if there is one or more Internet email recipients.
4729 if (CCC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4730 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4731 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4732 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4734 /* If we're only checking the validity of the request, return
4735 * success without creating the message.
4738 cprintf("%d %s|%d\n", CIT_OK,
4739 ((valid_to != NULL) ? valid_to->display_recp : ""),
4741 free_recipients(valid_to);
4742 free_recipients(valid_cc);
4743 free_recipients(valid_bcc);
4747 /* We don't need these anymore because we'll do it differently below */
4748 free_recipients(valid_to);
4749 free_recipients(valid_cc);
4750 free_recipients(valid_bcc);
4752 /* Read in the message from the client. */
4754 cprintf("%d send message\n", START_CHAT_MODE);
4756 cprintf("%d send message\n", SEND_LISTING);
4759 msg = CtdlMakeMessage(&CCC->user, recp, cc,
4760 CCC->room.QRname, anonymous, format_type,
4761 newusername, newuseremail, subject,
4762 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4765 /* Put together one big recipients struct containing to/cc/bcc all in
4766 * one. This is for the envelope.
4768 char *all_recps = malloc(SIZ * 3);
4769 strcpy(all_recps, recp);
4770 if (!IsEmptyStr(cc)) {
4771 if (!IsEmptyStr(all_recps)) {
4772 strcat(all_recps, ",");
4774 strcat(all_recps, cc);
4776 if (!IsEmptyStr(bcc)) {
4777 if (!IsEmptyStr(all_recps)) {
4778 strcat(all_recps, ",");
4780 strcat(all_recps, bcc);
4782 if (!IsEmptyStr(all_recps)) {
4783 valid = validate_recipients(all_recps, NULL, 0);
4790 if ((valid != NULL) && (valid->num_room == 1))
4792 /* posting into an ML room? set the envelope from
4793 * to the actual mail address so others get a valid
4796 msg->cm_fields[eenVelopeTo] = strdup(valid->recp_orgroom);
4800 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4802 cprintf("%ld\n", msgnum);
4804 if (StrLength(CCC->StatusMessage) > 0) {
4805 cprintf("%s\n", ChrPtr(CCC->StatusMessage));
4807 else if (msgnum >= 0L) {
4808 client_write(HKEY("Message accepted.\n"));
4811 client_write(HKEY("Internal error.\n"));
4814 if (msg->cm_fields[eExclusiveID] != NULL) {
4815 cprintf("%s\n", msg->cm_fields[eExclusiveID]);
4822 CtdlFreeMessage(msg);
4824 if (valid != NULL) {
4825 free_recipients(valid);
4833 * API function to delete messages which match a set of criteria
4834 * (returns the actual number of messages deleted)
4836 int CtdlDeleteMessages(char *room_name, /* which room */
4837 long *dmsgnums, /* array of msg numbers to be deleted */
4838 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4839 char *content_type /* or "" for any. regular expressions expected. */
4842 struct CitContext *CCC = CC;
4843 struct ctdlroom qrbuf;
4844 struct cdbdata *cdbfr;
4845 long *msglist = NULL;
4846 long *dellist = NULL;
4849 int num_deleted = 0;
4851 struct MetaData smi;
4854 int need_to_free_re = 0;
4856 if (content_type) if (!IsEmptyStr(content_type)) {
4857 regcomp(&re, content_type, 0);
4858 need_to_free_re = 1;
4860 MSG_syslog(LOG_DEBUG, " CtdlDeleteMessages(%s, %d msgs, %s)\n",
4861 room_name, num_dmsgnums, content_type);
4863 /* get room record, obtaining a lock... */
4864 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4865 MSG_syslog(LOG_ERR, " CtdlDeleteMessages(): Room <%s> not found\n",
4867 if (need_to_free_re) regfree(&re);
4868 return (0); /* room not found */
4870 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4872 if (cdbfr != NULL) {
4873 dellist = malloc(cdbfr->len);
4874 msglist = (long *) cdbfr->ptr;
4875 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4876 num_msgs = cdbfr->len / sizeof(long);
4880 int have_contenttype = (content_type != NULL) && !IsEmptyStr(content_type);
4881 int have_delmsgs = (num_dmsgnums == 0) || (dmsgnums == NULL);
4882 int have_more_del = 1;
4884 num_msgs = sort_msglist(msglist, num_msgs);
4885 if (num_dmsgnums > 1)
4886 num_dmsgnums = sort_msglist(dmsgnums, num_dmsgnums);
4889 StrBuf *dbg = NewStrBuf();
4890 for (i = 0; i < num_dmsgnums; i++)
4891 StrBufAppendPrintf(dbg, ", %ld", dmsgnums[i]);
4892 MSG_syslog(LOG_DEBUG, " Deleting before: %s", ChrPtr(dbg));
4897 while ((i < num_msgs) && (have_more_del)) {
4900 /* Set/clear a bit for each criterion */
4902 /* 0 messages in the list or a null list means that we are
4903 * interested in deleting any messages which meet the other criteria.
4906 delete_this |= 0x01;
4909 while ((i < num_msgs) && (msglist[i] < dmsgnums[j])) i++;
4914 if (msglist[i] == dmsgnums[j]) {
4915 delete_this |= 0x01;
4918 have_more_del = (j < num_dmsgnums);
4921 if (have_contenttype) {
4922 GetMetaData(&smi, msglist[i]);
4923 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4924 delete_this |= 0x02;
4927 delete_this |= 0x02;
4930 /* Delete message only if all bits are set */
4931 if (delete_this == 0x03) {
4932 dellist[num_deleted++] = msglist[i];
4939 StrBuf *dbg = NewStrBuf();
4940 for (i = 0; i < num_deleted; i++)
4941 StrBufAppendPrintf(dbg, ", %ld", dellist[i]);
4942 MSG_syslog(LOG_DEBUG, " Deleting: %s", ChrPtr(dbg));
4946 num_msgs = sort_msglist(msglist, num_msgs);
4947 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4948 msglist, (int)(num_msgs * sizeof(long)));
4951 qrbuf.QRhighest = msglist[num_msgs - 1];
4953 qrbuf.QRhighest = 0;
4955 CtdlPutRoomLock(&qrbuf);
4957 /* Go through the messages we pulled out of the index, and decrement
4958 * their reference counts by 1. If this is the only room the message
4959 * was in, the reference count will reach zero and the message will
4960 * automatically be deleted from the database. We do this in a
4961 * separate pass because there might be plug-in hooks getting called,
4962 * and we don't want that happening during an S_ROOMS critical
4966 for (i=0; i<num_deleted; ++i) {
4967 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4969 AdjRefCountList(dellist, num_deleted, -1);
4971 /* Now free the memory we used, and go away. */
4972 if (msglist != NULL) free(msglist);
4973 if (dellist != NULL) free(dellist);
4974 MSG_syslog(LOG_DEBUG, " %d message(s) deleted.\n", num_deleted);
4975 if (need_to_free_re) regfree(&re);
4976 return (num_deleted);
4982 * Check whether the current user has permission to delete messages from
4983 * the current room (returns 1 for yes, 0 for no)
4985 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4987 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4988 if (ra & UA_DELETEALLOWED) return(1);
4996 * Delete message from current room
4998 void cmd_dele(char *args)
5007 extract_token(msgset, args, 0, '|', sizeof msgset);
5008 num_msgs = num_tokens(msgset, ',');
5010 cprintf("%d Nothing to do.\n", CIT_OK);
5014 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
5015 cprintf("%d Higher access required.\n",
5016 ERROR + HIGHER_ACCESS_REQUIRED);
5021 * Build our message set to be moved/copied
5023 msgs = malloc(num_msgs * sizeof(long));
5024 for (i=0; i<num_msgs; ++i) {
5025 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5026 msgs[i] = atol(msgtok);
5029 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5033 cprintf("%d %d message%s deleted.\n", CIT_OK,
5034 num_deleted, ((num_deleted != 1) ? "s" : ""));
5036 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
5044 * move or copy a message to another room
5046 void cmd_move(char *args)
5053 char targ[ROOMNAMELEN];
5054 struct ctdlroom qtemp;
5061 extract_token(msgset, args, 0, '|', sizeof msgset);
5062 num_msgs = num_tokens(msgset, ',');
5064 cprintf("%d Nothing to do.\n", CIT_OK);
5068 extract_token(targ, args, 1, '|', sizeof targ);
5069 convert_room_name_macros(targ, sizeof targ);
5070 targ[ROOMNAMELEN - 1] = 0;
5071 is_copy = extract_int(args, 2);
5073 if (CtdlGetRoom(&qtemp, targ) != 0) {
5074 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
5078 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
5079 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
5083 CtdlGetUser(&CC->user, CC->curr_user);
5084 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
5086 /* Check for permission to perform this operation.
5087 * Remember: "CC->room" is source, "qtemp" is target.
5091 /* Admins can move/copy */
5092 if (CC->user.axlevel >= AxAideU) permit = 1;
5094 /* Room aides can move/copy */
5095 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
5097 /* Permit move/copy from personal rooms */
5098 if ((CC->room.QRflags & QR_MAILBOX)
5099 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5101 /* Permit only copy from public to personal room */
5103 && (!(CC->room.QRflags & QR_MAILBOX))
5104 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
5106 /* Permit message removal from collaborative delete rooms */
5107 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
5109 /* Users allowed to post into the target room may move into it too. */
5110 if ((CC->room.QRflags & QR_MAILBOX) &&
5111 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
5113 /* User must have access to target room */
5114 if (!(ra & UA_KNOWN)) permit = 0;
5117 cprintf("%d Higher access required.\n",
5118 ERROR + HIGHER_ACCESS_REQUIRED);
5123 * Build our message set to be moved/copied
5125 msgs = malloc(num_msgs * sizeof(long));
5126 for (i=0; i<num_msgs; ++i) {
5127 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
5128 msgs[i] = atol(msgtok);
5134 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
5136 cprintf("%d Cannot store message(s) in %s: error %d\n",
5142 /* Now delete the message from the source room,
5143 * if this is a 'move' rather than a 'copy' operation.
5146 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
5150 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
5156 * GetMetaData() - Get the supplementary record for a message
5158 void GetMetaData(struct MetaData *smibuf, long msgnum)
5161 struct cdbdata *cdbsmi;
5164 memset(smibuf, 0, sizeof(struct MetaData));
5165 smibuf->meta_msgnum = msgnum;
5166 smibuf->meta_refcount = 1; /* Default reference count is 1 */
5168 /* Use the negative of the message number for its supp record index */
5169 TheIndex = (0L - msgnum);
5171 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
5172 if (cdbsmi == NULL) {
5173 return; /* record not found; go with defaults */
5175 memcpy(smibuf, cdbsmi->ptr,
5176 ((cdbsmi->len > sizeof(struct MetaData)) ?
5177 sizeof(struct MetaData) : cdbsmi->len));
5184 * PutMetaData() - (re)write supplementary record for a message
5186 void PutMetaData(struct MetaData *smibuf)
5190 /* Use the negative of the message number for the metadata db index */
5191 TheIndex = (0L - smibuf->meta_msgnum);
5193 cdb_store(CDB_MSGMAIN,
5194 &TheIndex, (int)sizeof(long),
5195 smibuf, (int)sizeof(struct MetaData));
5200 * AdjRefCount - submit an adjustment to the reference count for a message.
5201 * (These are just queued -- we actually process them later.)
5203 void AdjRefCount(long msgnum, int incr)
5205 struct CitContext *CCC = CC;
5206 struct arcq new_arcq;
5209 MSG_syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n", msgnum, incr);
5211 begin_critical_section(S_SUPPMSGMAIN);
5212 if (arcfp == NULL) {
5213 arcfp = fopen(file_arcq, "ab+");
5214 chown(file_arcq, CTDLUID, (-1));
5215 chmod(file_arcq, 0600);
5217 end_critical_section(S_SUPPMSGMAIN);
5219 /* msgnum < 0 means that we're trying to close the file */
5221 MSGM_syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
5222 begin_critical_section(S_SUPPMSGMAIN);
5223 if (arcfp != NULL) {
5227 end_critical_section(S_SUPPMSGMAIN);
5232 * If we can't open the queue, perform the operation synchronously.
5234 if (arcfp == NULL) {
5235 TDAP_AdjRefCount(msgnum, incr);
5239 new_arcq.arcq_msgnum = msgnum;
5240 new_arcq.arcq_delta = incr;
5241 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
5243 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5252 void AdjRefCountList(long *msgnum, long nmsg, int incr)
5254 struct CitContext *CCC = CC;
5255 long i, the_size, offset;
5256 struct arcq *new_arcq;
5259 MSG_syslog(LOG_DEBUG, "AdjRefCountList() msg %ld ref count delta %+d\n", nmsg, incr);
5261 begin_critical_section(S_SUPPMSGMAIN);
5262 if (arcfp == NULL) {
5263 arcfp = fopen(file_arcq, "ab+");
5264 chown(file_arcq, CTDLUID, (-1));
5265 chmod(file_arcq, 0600);
5267 end_critical_section(S_SUPPMSGMAIN);
5270 * If we can't open the queue, perform the operation synchronously.
5272 if (arcfp == NULL) {
5273 for (i = 0; i < nmsg; i++)
5274 TDAP_AdjRefCount(msgnum[i], incr);
5278 the_size = sizeof(struct arcq) * nmsg;
5279 new_arcq = malloc(the_size);
5280 for (i = 0; i < nmsg; i++) {
5281 new_arcq[i].arcq_msgnum = msgnum[i];
5282 new_arcq[i].arcq_delta = incr;
5286 while ((rv >= 0) && (offset < the_size))
5288 rv = fwrite(new_arcq + offset, 1, the_size - offset, arcfp);
5290 MSG_syslog(LOG_EMERG, "Couldn't write Refcount Queue File %s: %s\n",
5306 * TDAP_ProcessAdjRefCountQueue()
5308 * Process the queue of message count adjustments that was created by calls
5309 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
5310 * for each one. This should be an "off hours" operation.
5312 int TDAP_ProcessAdjRefCountQueue(void)
5314 struct CitContext *CCC = CC;
5315 char file_arcq_temp[PATH_MAX];
5318 struct arcq arcq_rec;
5319 int num_records_processed = 0;
5321 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
5323 begin_critical_section(S_SUPPMSGMAIN);
5324 if (arcfp != NULL) {
5329 r = link(file_arcq, file_arcq_temp);
5331 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5332 end_critical_section(S_SUPPMSGMAIN);
5333 return(num_records_processed);
5337 end_critical_section(S_SUPPMSGMAIN);
5339 fp = fopen(file_arcq_temp, "rb");
5341 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5342 return(num_records_processed);
5345 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5346 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5347 ++num_records_processed;
5351 r = unlink(file_arcq_temp);
5353 MSG_syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5356 return(num_records_processed);
5362 * TDAP_AdjRefCount - adjust the reference count for a message.
5363 * This one does it "for real" because it's called by
5364 * the autopurger function that processes the queue
5365 * created by AdjRefCount(). If a message's reference
5366 * count becomes zero, we also delete the message from
5367 * disk and de-index it.
5369 void TDAP_AdjRefCount(long msgnum, int incr)
5371 struct CitContext *CCC = CC;
5373 struct MetaData smi;
5376 /* This is a *tight* critical section; please keep it that way, as
5377 * it may get called while nested in other critical sections.
5378 * Complicating this any further will surely cause deadlock!
5380 begin_critical_section(S_SUPPMSGMAIN);
5381 GetMetaData(&smi, msgnum);
5382 smi.meta_refcount += incr;
5384 end_critical_section(S_SUPPMSGMAIN);
5385 MSG_syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5386 msgnum, incr, smi.meta_refcount
5389 /* If the reference count is now zero, delete the message
5390 * (and its supplementary record as well).
5392 if (smi.meta_refcount == 0) {
5393 MSG_syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5395 /* Call delete hooks with NULL room to show it has gone altogether */
5396 PerformDeleteHooks(NULL, msgnum);
5398 /* Remove from message base */
5400 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5401 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5403 /* Remove metadata record */
5404 delnum = (0L - msgnum);
5405 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5411 * Write a generic object to this room
5413 * Note: this could be much more efficient. Right now we use two temporary
5414 * files, and still pull the message into memory as with all others.
5416 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5417 char *content_type, /* MIME type of this object */
5418 char *raw_message, /* Data to be written */
5419 off_t raw_length, /* Size of raw_message */
5420 struct ctdluser *is_mailbox, /* Mailbox room? */
5421 int is_binary, /* Is encoding necessary? */
5422 int is_unique, /* Del others of this type? */
5423 unsigned int flags /* Internal save flags */
5426 struct CitContext *CCC = CC;
5427 struct ctdlroom qrbuf;
5428 char roomname[ROOMNAMELEN];
5429 struct CtdlMessage *msg;
5430 char *encoded_message = NULL;
5432 if (is_mailbox != NULL) {
5433 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5436 safestrncpy(roomname, req_room, sizeof(roomname));
5439 MSG_syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5442 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5445 encoded_message = malloc((size_t)(raw_length + 4096));
5448 sprintf(encoded_message, "Content-type: %s\n", content_type);
5451 sprintf(&encoded_message[strlen(encoded_message)],
5452 "Content-transfer-encoding: base64\n\n"
5456 sprintf(&encoded_message[strlen(encoded_message)],
5457 "Content-transfer-encoding: 7bit\n\n"
5463 &encoded_message[strlen(encoded_message)],
5471 &encoded_message[strlen(encoded_message)],
5477 MSGM_syslog(LOG_DEBUG, "Allocating\n");
5478 msg = malloc(sizeof(struct CtdlMessage));
5479 memset(msg, 0, sizeof(struct CtdlMessage));
5480 msg->cm_magic = CTDLMESSAGE_MAGIC;
5481 msg->cm_anon_type = MES_NORMAL;
5482 msg->cm_format_type = 4;
5483 msg->cm_fields[eAuthor] = strdup(CCC->user.fullname);
5484 msg->cm_fields[eOriginalRoom] = strdup(req_room);
5485 msg->cm_fields[eNodeName] = strdup(config.c_nodename);
5486 msg->cm_fields[eHumanNode] = strdup(config.c_humannode);
5487 msg->cm_flags = flags;
5489 msg->cm_fields[eMesageText] = encoded_message;
5491 /* Create the requested room if we have to. */
5492 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5493 CtdlCreateRoom(roomname,
5494 ( (is_mailbox != NULL) ? 5 : 3 ),
5495 "", 0, 1, 0, VIEW_BBS);
5497 /* If the caller specified this object as unique, delete all
5498 * other objects of this type that are currently in the room.
5501 MSG_syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5502 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5505 /* Now write the data */
5506 CtdlSubmitMsg(msg, NULL, roomname, 0);
5507 CtdlFreeMessage(msg);
5515 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5516 config_msgnum = msgnum;
5520 char *CtdlGetSysConfig(char *sysconfname) {
5521 char hold_rm[ROOMNAMELEN];
5524 struct CtdlMessage *msg;
5527 strcpy(hold_rm, CC->room.QRname);
5528 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5529 CtdlGetRoom(&CC->room, hold_rm);
5534 /* We want the last (and probably only) config in this room */
5535 begin_critical_section(S_CONFIG);
5536 config_msgnum = (-1L);
5537 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5538 CtdlGetSysConfigBackend, NULL);
5539 msgnum = config_msgnum;
5540 end_critical_section(S_CONFIG);
5546 msg = CtdlFetchMessage(msgnum, 1);
5548 conf = strdup(msg->cm_fields[eMesageText]);
5549 CtdlFreeMessage(msg);
5556 CtdlGetRoom(&CC->room, hold_rm);
5558 if (conf != NULL) do {
5559 extract_token(buf, conf, 0, '\n', sizeof buf);
5560 strcpy(conf, &conf[strlen(buf)+1]);
5561 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5567 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5568 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5573 * Determine whether a given Internet address belongs to the current user
5575 int CtdlIsMe(char *addr, int addr_buf_len)
5577 struct recptypes *recp;
5580 recp = validate_recipients(addr, NULL, 0);
5581 if (recp == NULL) return(0);
5583 if (recp->num_local == 0) {
5584 free_recipients(recp);
5588 for (i=0; i<recp->num_local; ++i) {
5589 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5590 if (!strcasecmp(addr, CC->user.fullname)) {
5591 free_recipients(recp);
5596 free_recipients(recp);
5602 * Citadel protocol command to do the same
5604 void cmd_isme(char *argbuf) {
5607 if (CtdlAccessCheck(ac_logged_in)) return;
5608 extract_token(addr, argbuf, 0, '|', sizeof addr);
5610 if (CtdlIsMe(addr, sizeof addr)) {
5611 cprintf("%d %s\n", CIT_OK, addr);
5614 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5620 /*****************************************************************************/
5621 /* MODULE INITIALIZATION STUFF */
5622 /*****************************************************************************/
5623 void SetMessageDebugEnabled(const int n)
5625 MessageDebugEnabled = n;
5627 CTDL_MODULE_INIT(msgbase)
5630 CtdlRegisterDebugFlagHook(HKEY("messages"), SetMessageDebugEnabled, &MessageDebugEnabled);
5632 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5633 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5634 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5635 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5636 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5637 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5638 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5639 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5640 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5641 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5642 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5643 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5646 /* return our Subversion id for the Log */