4 * Implements the message store.
6 * Copyright (c) 1987-2010 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
34 # include <sys/time.h>
47 #include <sys/types.h>
49 #include <libcitadel.h>
52 #include "serv_extensions.h"
56 #include "sysdep_decls.h"
57 #include "citserver.h"
64 #include "internet_addressing.h"
65 #include "euidindex.h"
66 #include "journaling.h"
67 #include "citadel_dirs.h"
68 #include "clientsocket.h"
69 #include "serv_network.h"
72 #include "ctdl_module.h"
75 struct addresses_to_be_filed *atbf = NULL;
77 /* This temp file holds the queue of operations for AdjRefCount() */
78 static FILE *arcfp = NULL;
81 * This really belongs in serv_network.c, but I don't know how to export
82 * symbols between modules.
84 struct FilterList *filterlist = NULL;
88 * These are the four-character field headers we use when outputting
89 * messages in Citadel format (as opposed to RFC822 format).
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
98 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
99 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
128 * This function is self explanatory.
129 * (What can I say, I'm in a weird mood today...)
131 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
135 for (i = 0; i < strlen(name); ++i) {
136 if (name[i] == '@') {
137 while (isspace(name[i - 1]) && i > 0) {
138 strcpy(&name[i - 1], &name[i]);
141 while (isspace(name[i + 1])) {
142 strcpy(&name[i + 1], &name[i + 2]);
150 * Aliasing for network mail.
151 * (Error messages have been commented out, because this is a server.)
153 int alias(char *name)
154 { /* process alias and routing info for mail */
157 char aaa[SIZ], bbb[SIZ];
158 char *ignetcfg = NULL;
159 char *ignetmap = NULL;
165 char original_name[256];
166 safestrncpy(original_name, name, sizeof original_name);
169 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170 stripallbut(name, '<', '>');
172 fp = fopen(file_mail_aliases, "r");
174 fp = fopen("/dev/null", "r");
181 while (fgets(aaa, sizeof aaa, fp) != NULL) {
182 while (isspace(name[0]))
183 strcpy(name, &name[1]);
184 aaa[strlen(aaa) - 1] = 0;
186 for (a = 0; a < strlen(aaa); ++a) {
188 strcpy(bbb, &aaa[a + 1]);
192 if (!strcasecmp(name, aaa))
197 /* Hit the Global Address Book */
198 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
202 if (strcasecmp(original_name, name)) {
203 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
206 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207 for (a=0; a<strlen(name); ++a) {
208 if (name[a] == '@') {
209 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
211 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
216 /* determine local or remote type, see citadel.h */
217 at = haschar(name, '@');
218 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
219 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
220 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
222 /* figure out the delivery mode */
223 extract_token(node, name, 1, '@', sizeof node);
225 /* If there are one or more dots in the nodename, we assume that it
226 * is an FQDN and will attempt SMTP delivery to the Internet.
228 if (haschar(node, '.') > 0) {
229 return(MES_INTERNET);
232 /* Otherwise we look in the IGnet maps for a valid Citadel node.
233 * Try directly-connected nodes first...
235 ignetcfg = CtdlGetSysConfig(IGNETCFG);
236 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
247 * Then try nodes that are two or more hops away.
249 ignetmap = CtdlGetSysConfig(IGNETMAP);
250 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252 extract_token(testnode, buf, 0, '|', sizeof testnode);
253 if (!strcasecmp(node, testnode)) {
260 /* If we get to this point it's an invalid node name */
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
300 * Back end for the MSGS command: output EUID header.
302 void headers_euid(long msgnum, void *userdata)
304 struct CtdlMessage *msg;
306 msg = CtdlFetchMessage(msgnum, 0);
308 cprintf("%ld||\n", msgnum);
312 cprintf("%ld|%s|\n", msgnum, (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""));
313 CtdlFreeMessage(msg);
320 /* Determine if a given message matches the fields in a message template.
321 * Return 0 for a successful match.
323 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
326 /* If there aren't any fields in the template, all messages will
329 if (template == NULL) return(0);
331 /* Null messages are bogus. */
332 if (msg == NULL) return(1);
334 for (i='A'; i<='Z'; ++i) {
335 if (template->cm_fields[i] != NULL) {
336 if (msg->cm_fields[i] == NULL) {
337 /* Considered equal if temmplate is empty string */
338 if (IsEmptyStr(template->cm_fields[i])) continue;
341 if (strcasecmp(msg->cm_fields[i],
342 template->cm_fields[i])) return 1;
346 /* All compares succeeded: we have a match! */
353 * Retrieve the "seen" message list for the current room.
355 void CtdlGetSeen(char *buf, int which_set) {
358 /* Learn about the user and room in question */
359 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
361 if (which_set == ctdlsetseen_seen)
362 safestrncpy(buf, vbuf.v_seen, SIZ);
363 if (which_set == ctdlsetseen_answered)
364 safestrncpy(buf, vbuf.v_answered, SIZ);
370 * Manipulate the "seen msgs" string (or other message set strings)
372 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
373 int target_setting, int which_set,
374 struct ctdluser *which_user, struct ctdlroom *which_room) {
375 struct cdbdata *cdbfr;
389 char *is_set; /* actually an array of booleans */
391 /* Don't bother doing *anything* if we were passed a list of zero messages */
392 if (num_target_msgnums < 1) {
396 /* If no room was specified, we go with the current room. */
398 which_room = &CC->room;
401 /* If no user was specified, we go with the current user. */
403 which_user = &CC->user;
406 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
407 num_target_msgnums, target_msgnums[0],
408 (target_setting ? "SET" : "CLEAR"),
412 /* Learn about the user and room in question */
413 CtdlGetRelationship(&vbuf, which_user, which_room);
415 /* Load the message list */
416 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
418 msglist = (long *) cdbfr->ptr;
419 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
420 num_msgs = cdbfr->len / sizeof(long);
423 return; /* No messages at all? No further action. */
426 is_set = malloc(num_msgs * sizeof(char));
427 memset(is_set, 0, (num_msgs * sizeof(char)) );
429 /* Decide which message set we're manipulating */
431 case ctdlsetseen_seen:
432 vset = NewStrBufPlain(vbuf.v_seen, -1);
434 case ctdlsetseen_answered:
435 vset = NewStrBufPlain(vbuf.v_answered, -1);
442 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
443 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
444 for (i=0; i<num_msgs; ++i) {
445 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
447 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
448 for (k=0; k<num_target_msgnums; ++k) {
449 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
453 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
455 /* Translate the existing sequence set into an array of booleans */
456 setstr = NewStrBuf();
460 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
462 StrBufExtract_token(lostr, setstr, 0, ':');
463 if (StrBufNum_tokens(setstr, ':') >= 2) {
464 StrBufExtract_token(histr, setstr, 1, ':');
468 StrBufAppendBuf(histr, lostr, 0);
471 if (!strcmp(ChrPtr(histr), "*")) {
478 for (i = 0; i < num_msgs; ++i) {
479 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
489 /* Now translate the array of booleans back into a sequence set */
495 for (i=0; i<num_msgs; ++i) {
499 for (k=0; k<num_target_msgnums; ++k) {
500 if (msglist[i] == target_msgnums[k]) {
501 is_seen = target_setting;
505 if ((was_seen == 0) && (is_seen == 1)) {
508 else if ((was_seen == 1) && (is_seen == 0)) {
511 if (StrLength(vset) > 0) {
512 StrBufAppendBufPlain(vset, HKEY(","), 0);
515 StrBufAppendPrintf(vset, "%ld", hi);
518 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
522 if ((is_seen) && (i == num_msgs - 1)) {
523 if (StrLength(vset) > 0) {
524 StrBufAppendBufPlain(vset, HKEY(","), 0);
526 if ((i==0) || (was_seen == 0)) {
527 StrBufAppendPrintf(vset, "%ld", msglist[i]);
530 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
538 * We will have to stuff this string back into a 4096 byte buffer, so if it's
539 * larger than that now, truncate it by removing tokens from the beginning.
540 * The limit of 100 iterations is there to prevent an infinite loop in case
541 * something unexpected happens.
543 int number_of_truncations = 0;
544 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
545 StrBufRemove_token(vset, 0, ',');
546 ++number_of_truncations;
550 * If we're truncating the sequence set of messages marked with the 'seen' flag,
551 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
552 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
554 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
556 first_tok = NewStrBuf();
557 StrBufExtract_token(first_tok, vset, 0, ',');
558 StrBufRemove_token(vset, 0, ',');
560 if (StrBufNum_tokens(first_tok, ':') > 1) {
561 StrBufRemove_token(first_tok, 0, ':');
565 new_set = NewStrBuf();
566 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
567 StrBufAppendBuf(new_set, first_tok, 0);
568 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
569 StrBufAppendBuf(new_set, vset, 0);
572 FreeStrBuf(&first_tok);
576 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
578 /* Decide which message set we're manipulating */
580 case ctdlsetseen_seen:
581 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
583 case ctdlsetseen_answered:
584 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
590 CtdlSetRelationship(&vbuf, which_user, which_room);
596 * API function to perform an operation for each qualifying message in the
597 * current room. (Returns the number of messages processed.)
599 int CtdlForEachMessage(int mode, long ref, char *search_string,
601 struct CtdlMessage *compare,
602 ForEachMsgCallback CallBack,
608 struct cdbdata *cdbfr;
609 long *msglist = NULL;
611 int num_processed = 0;
614 struct CtdlMessage *msg = NULL;
617 int printed_lastold = 0;
618 int num_search_msgs = 0;
619 long *search_msgs = NULL;
621 int need_to_free_re = 0;
624 if ((content_type) && (!IsEmptyStr(content_type))) {
625 regcomp(&re, content_type, 0);
629 /* Learn about the user and room in question */
630 CtdlGetUser(&CC->user, CC->curr_user);
631 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
633 /* Load the message list */
634 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
636 msglist = (long *) cdbfr->ptr;
637 num_msgs = cdbfr->len / sizeof(long);
639 if (need_to_free_re) regfree(&re);
640 return 0; /* No messages at all? No further action. */
645 * Now begin the traversal.
647 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
649 /* If the caller is looking for a specific MIME type, filter
650 * out all messages which are not of the type requested.
652 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
654 /* This call to GetMetaData() sits inside this loop
655 * so that we only do the extra database read per msg
656 * if we need to. Doing the extra read all the time
657 * really kills the server. If we ever need to use
658 * metadata for another search criterion, we need to
659 * move the read somewhere else -- but still be smart
660 * enough to only do the read if the caller has
661 * specified something that will need it.
663 GetMetaData(&smi, msglist[a]);
665 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
666 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
672 num_msgs = sort_msglist(msglist, num_msgs);
674 /* If a template was supplied, filter out the messages which
675 * don't match. (This could induce some delays!)
678 if (compare != NULL) {
679 for (a = 0; a < num_msgs; ++a) {
680 msg = CtdlFetchMessage(msglist[a], 1);
682 if (CtdlMsgCmp(msg, compare)) {
685 CtdlFreeMessage(msg);
691 /* If a search string was specified, get a message list from
692 * the full text index and remove messages which aren't on both
696 * Since the lists are sorted and strictly ascending, and the
697 * output list is guaranteed to be shorter than or equal to the
698 * input list, we overwrite the bottom of the input list. This
699 * eliminates the need to memmove big chunks of the list over and
702 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
704 /* Call search module via hook mechanism.
705 * NULL means use any search function available.
706 * otherwise replace with a char * to name of search routine
708 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
710 if (num_search_msgs > 0) {
714 orig_num_msgs = num_msgs;
716 for (i=0; i<orig_num_msgs; ++i) {
717 for (j=0; j<num_search_msgs; ++j) {
718 if (msglist[i] == search_msgs[j]) {
719 msglist[num_msgs++] = msglist[i];
725 num_msgs = 0; /* No messages qualify */
727 if (search_msgs != NULL) free(search_msgs);
729 /* Now that we've purged messages which don't contain the search
730 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
737 * Now iterate through the message list, according to the
738 * criteria supplied by the caller.
741 for (a = 0; a < num_msgs; ++a) {
742 thismsg = msglist[a];
743 if (mode == MSGS_ALL) {
747 is_seen = is_msg_in_sequence_set(
748 vbuf.v_seen, thismsg);
749 if (is_seen) lastold = thismsg;
755 || ((mode == MSGS_OLD) && (is_seen))
756 || ((mode == MSGS_NEW) && (!is_seen))
757 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
758 || ((mode == MSGS_FIRST) && (a < ref))
759 || ((mode == MSGS_GT) && (thismsg > ref))
760 || ((mode == MSGS_LT) && (thismsg < ref))
761 || ((mode == MSGS_EQ) && (thismsg == ref))
764 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
766 CallBack(lastold, userdata);
770 if (CallBack) CallBack(thismsg, userdata);
774 cdb_free(cdbfr); /* Clean up */
775 if (need_to_free_re) regfree(&re);
776 return num_processed;
782 * cmd_msgs() - get list of message #'s in this room
783 * implements the MSGS server command using CtdlForEachMessage()
785 void cmd_msgs(char *cmdbuf)
794 int with_template = 0;
795 struct CtdlMessage *template = NULL;
796 char search_string[1024];
797 ForEachMsgCallback CallBack;
799 extract_token(which, cmdbuf, 0, '|', sizeof which);
800 cm_ref = extract_int(cmdbuf, 1);
801 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
802 with_template = extract_int(cmdbuf, 2);
803 switch (extract_int(cmdbuf, 3))
807 CallBack = simple_listing;
810 CallBack = headers_listing;
813 CallBack = headers_euid;
818 if (!strncasecmp(which, "OLD", 3))
820 else if (!strncasecmp(which, "NEW", 3))
822 else if (!strncasecmp(which, "FIRST", 5))
824 else if (!strncasecmp(which, "LAST", 4))
826 else if (!strncasecmp(which, "GT", 2))
828 else if (!strncasecmp(which, "LT", 2))
830 else if (!strncasecmp(which, "SEARCH", 6))
835 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
836 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
840 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
841 cprintf("%d Full text index is not enabled on this server.\n",
842 ERROR + CMD_NOT_SUPPORTED);
848 cprintf("%d Send template then receive message list\n",
850 template = (struct CtdlMessage *)
851 malloc(sizeof(struct CtdlMessage));
852 memset(template, 0, sizeof(struct CtdlMessage));
853 template->cm_magic = CTDLMESSAGE_MAGIC;
854 template->cm_anon_type = MES_NORMAL;
856 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
857 extract_token(tfield, buf, 0, '|', sizeof tfield);
858 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
859 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
860 if (!strcasecmp(tfield, msgkeys[i])) {
861 template->cm_fields[i] =
869 cprintf("%d \n", LISTING_FOLLOWS);
872 CtdlForEachMessage(mode,
873 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
874 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
879 if (template != NULL) CtdlFreeMessage(template);
887 * help_subst() - support routine for help file viewer
889 void help_subst(char *strbuf, char *source, char *dest)
894 while (p = pattern2(strbuf, source), (p >= 0)) {
895 strcpy(workbuf, &strbuf[p + strlen(source)]);
896 strcpy(&strbuf[p], dest);
897 strcat(strbuf, workbuf);
902 void do_help_subst(char *buffer)
906 help_subst(buffer, "^nodename", config.c_nodename);
907 help_subst(buffer, "^humannode", config.c_humannode);
908 help_subst(buffer, "^fqdn", config.c_fqdn);
909 help_subst(buffer, "^username", CC->user.fullname);
910 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
911 help_subst(buffer, "^usernum", buf2);
912 help_subst(buffer, "^sysadm", config.c_sysadm);
913 help_subst(buffer, "^variantname", CITADEL);
914 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
915 help_subst(buffer, "^maxsessions", buf2);
916 help_subst(buffer, "^bbsdir", ctdl_message_dir);
922 * memfmout() - Citadel text formatter and paginator.
923 * Although the original purpose of this routine was to format
924 * text to the reader's screen width, all we're really using it
925 * for here is to format text out to 80 columns before sending it
926 * to the client. The client software may reformat it again.
929 char *mptr, /* where are we going to get our text from? */
930 const char *nl) /* string to terminate lines with */
938 int NLFound, NLFoundLastTime;
945 OutBuf = NewStrBufPlain(NULL, 200);
947 NLFound = NLFoundLastTime = 0;
950 LineStart = LastBlank = mptr;
953 for (i = 0; Found == 'x'; i++)
955 if (LineStart[i] == '\n')
957 else if (LineStart[i] == '\r')
959 else if (LineStart[i] == ' ')
960 LastBlank = &LineStart[i];
961 else if ((i > 80) && (LineStart != LastBlank))
963 else if (LineStart[i] == '\0')
969 if (LineStart[i + 1] == '\r')
970 mptr = &LineStart[i + 1];
972 mptr = &LineStart[i];
977 if (LineStart[i + 1] == '\n')
978 mptr = &LineStart[i + 1];
980 mptr = &LineStart[i];
985 mptr = &LineStart[i];
990 mptr = LastBlank + 1;
991 i = LastBlank - LineStart;
999 StrBufPlain(OutBuf, HKEY(" "));
1001 FlushStrBuf(OutBuf);
1002 StrBufAppendBufPlain(OutBuf, LineStart, i, 0);
1003 StrBufAppendBufPlain(OutBuf, nl, NLLen, 0);
1006 NLFoundLastTime = NLFound;
1007 } while (*mptr != '\0');
1009 FreeStrBuf(&OutBuf);
1015 * Callback function for mime parser that simply lists the part
1017 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1018 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1019 char *cbid, void *cbuserdata)
1023 ma = (struct ma_info *)cbuserdata;
1024 if (ma->is_ma == 0) {
1025 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
1026 name, filename, partnum, disp, cbtype, (long)length, cbid);
1031 * Callback function for multipart prefix
1033 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1034 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1035 char *cbid, void *cbuserdata)
1039 ma = (struct ma_info *)cbuserdata;
1040 if (!strcasecmp(cbtype, "multipart/alternative")) {
1044 if (ma->is_ma == 0) {
1045 cprintf("pref=%s|%s\n", partnum, cbtype);
1050 * Callback function for multipart sufffix
1052 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1053 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1054 char *cbid, void *cbuserdata)
1058 ma = (struct ma_info *)cbuserdata;
1059 if (ma->is_ma == 0) {
1060 cprintf("suff=%s|%s\n", partnum, cbtype);
1062 if (!strcasecmp(cbtype, "multipart/alternative")) {
1069 * Callback function for mime parser that opens a section for downloading
1071 void mime_download(char *name, char *filename, char *partnum, char *disp,
1072 void *content, char *cbtype, char *cbcharset, size_t length,
1073 char *encoding, char *cbid, void *cbuserdata)
1077 /* Silently go away if there's already a download open. */
1078 if (CC->download_fp != NULL)
1082 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1083 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1085 CC->download_fp = tmpfile();
1086 if (CC->download_fp == NULL)
1089 rv = fwrite(content, length, 1, CC->download_fp);
1090 fflush(CC->download_fp);
1091 rewind(CC->download_fp);
1093 OpenCmdResult(filename, cbtype);
1100 * Callback function for mime parser that outputs a section all at once.
1101 * We can specify the desired section by part number *or* content-id.
1103 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1104 void *content, char *cbtype, char *cbcharset, size_t length,
1105 char *encoding, char *cbid, void *cbuserdata)
1107 int *found_it = (int *)cbuserdata;
1110 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1111 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1114 cprintf("%d %d|-1|%s|%s\n",
1120 client_write(content, length);
1127 * Load a message from disk into memory.
1128 * This is used by CtdlOutputMsg() and other fetch functions.
1130 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1131 * using the CtdlMessageFree() function.
1133 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1135 struct cdbdata *dmsgtext;
1136 struct CtdlMessage *ret = NULL;
1140 cit_uint8_t field_header;
1142 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1144 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1145 if (dmsgtext == NULL) {
1148 mptr = dmsgtext->ptr;
1149 upper_bound = mptr + dmsgtext->len;
1151 /* Parse the three bytes that begin EVERY message on disk.
1152 * The first is always 0xFF, the on-disk magic number.
1153 * The second is the anonymous/public type byte.
1154 * The third is the format type byte (vari, fixed, or MIME).
1158 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1162 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1163 memset(ret, 0, sizeof(struct CtdlMessage));
1165 ret->cm_magic = CTDLMESSAGE_MAGIC;
1166 ret->cm_anon_type = *mptr++; /* Anon type byte */
1167 ret->cm_format_type = *mptr++; /* Format type byte */
1170 * The rest is zero or more arbitrary fields. Load them in.
1171 * We're done when we encounter either a zero-length field or
1172 * have just processed the 'M' (message text) field.
1175 if (mptr >= upper_bound) {
1178 field_header = *mptr++;
1179 ret->cm_fields[field_header] = strdup(mptr);
1181 while (*mptr++ != 0); /* advance to next field */
1183 } while ((mptr < upper_bound) && (field_header != 'M'));
1187 /* Always make sure there's something in the msg text field. If
1188 * it's NULL, the message text is most likely stored separately,
1189 * so go ahead and fetch that. Failing that, just set a dummy
1190 * body so other code doesn't barf.
1192 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1193 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1194 if (dmsgtext != NULL) {
1195 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1199 if (ret->cm_fields['M'] == NULL) {
1200 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1203 /* Perform "before read" hooks (aborting if any return nonzero) */
1204 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1205 CtdlFreeMessage(ret);
1214 * Returns 1 if the supplied pointer points to a valid Citadel message.
1215 * If the pointer is NULL or the magic number check fails, returns 0.
1217 int is_valid_message(struct CtdlMessage *msg) {
1220 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1221 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1229 * 'Destructor' for struct CtdlMessage
1231 void CtdlFreeMessage(struct CtdlMessage *msg)
1235 if (is_valid_message(msg) == 0)
1237 if (msg != NULL) free (msg);
1241 for (i = 0; i < 256; ++i)
1242 if (msg->cm_fields[i] != NULL) {
1243 free(msg->cm_fields[i]);
1246 msg->cm_magic = 0; /* just in case */
1252 * Pre callback function for multipart/alternative
1254 * NOTE: this differs from the standard behavior for a reason. Normally when
1255 * displaying multipart/alternative you want to show the _last_ usable
1256 * format in the message. Here we show the _first_ one, because it's
1257 * usually text/plain. Since this set of functions is designed for text
1258 * output to non-MIME-aware clients, this is the desired behavior.
1261 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1262 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1263 char *cbid, void *cbuserdata)
1267 ma = (struct ma_info *)cbuserdata;
1268 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1269 if (!strcasecmp(cbtype, "multipart/alternative")) {
1273 if (!strcasecmp(cbtype, "message/rfc822")) {
1279 * Post callback function for multipart/alternative
1281 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1282 void *content, char *cbtype, char *cbcharset, size_t length,
1283 char *encoding, char *cbid, void *cbuserdata)
1287 ma = (struct ma_info *)cbuserdata;
1288 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1289 if (!strcasecmp(cbtype, "multipart/alternative")) {
1293 if (!strcasecmp(cbtype, "message/rfc822")) {
1299 * Inline callback function for mime parser that wants to display text
1301 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1302 void *content, char *cbtype, char *cbcharset, size_t length,
1303 char *encoding, char *cbid, void *cbuserdata)
1310 ma = (struct ma_info *)cbuserdata;
1312 CtdlLogPrintf(CTDL_DEBUG,
1313 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1314 partnum, filename, cbtype, (long)length);
1317 * If we're in the middle of a multipart/alternative scope and
1318 * we've already printed another section, skip this one.
1320 if ( (ma->is_ma) && (ma->did_print) ) {
1321 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1326 if ( (!strcasecmp(cbtype, "text/plain"))
1327 || (IsEmptyStr(cbtype)) ) {
1330 client_write(wptr, length);
1331 if (wptr[length-1] != '\n') {
1338 if (!strcasecmp(cbtype, "text/html")) {
1339 ptr = html_to_ascii(content, length, 80, 0);
1341 client_write(ptr, wlen);
1342 if (ptr[wlen-1] != '\n') {
1349 if (ma->use_fo_hooks) {
1350 if (PerformFixedOutputHooks(cbtype, content, length)) {
1351 /* above function returns nonzero if it handled the part */
1356 if (strncasecmp(cbtype, "multipart/", 10)) {
1357 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1358 partnum, filename, cbtype, (long)length);
1364 * The client is elegant and sophisticated and wants to be choosy about
1365 * MIME content types, so figure out which multipart/alternative part
1366 * we're going to send.
1368 * We use a system of weights. When we find a part that matches one of the
1369 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1370 * and then set ma->chosen_pref to that MIME type's position in our preference
1371 * list. If we then hit another match, we only replace the first match if
1372 * the preference value is lower.
1374 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1375 void *content, char *cbtype, char *cbcharset, size_t length,
1376 char *encoding, char *cbid, void *cbuserdata)
1382 ma = (struct ma_info *)cbuserdata;
1384 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1385 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1386 // I don't know if there are any side effects! Please TEST TEST TEST
1387 //if (ma->is_ma > 0) {
1389 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1390 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1391 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1392 if (i < ma->chosen_pref) {
1393 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1394 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1395 ma->chosen_pref = i;
1402 * Now that we've chosen our preferred part, output it.
1404 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1405 void *content, char *cbtype, char *cbcharset, size_t length,
1406 char *encoding, char *cbid, void *cbuserdata)
1410 int add_newline = 0;
1414 ma = (struct ma_info *)cbuserdata;
1416 /* This is not the MIME part you're looking for... */
1417 if (strcasecmp(partnum, ma->chosen_part)) return;
1419 /* If the content-type of this part is in our preferred formats
1420 * list, we can simply output it verbatim.
1422 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1423 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1424 if (!strcasecmp(buf, cbtype)) {
1425 /* Yeah! Go! W00t!! */
1427 text_content = (char *)content;
1428 if (text_content[length-1] != '\n') {
1431 cprintf("Content-type: %s", cbtype);
1432 if (!IsEmptyStr(cbcharset)) {
1433 cprintf("; charset=%s", cbcharset);
1435 cprintf("\nContent-length: %d\n",
1436 (int)(length + add_newline) );
1437 if (!IsEmptyStr(encoding)) {
1438 cprintf("Content-transfer-encoding: %s\n", encoding);
1441 cprintf("Content-transfer-encoding: 7bit\n");
1443 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1445 client_write(content, length);
1446 if (add_newline) cprintf("\n");
1451 /* No translations required or possible: output as text/plain */
1452 cprintf("Content-type: text/plain\n\n");
1453 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1454 length, encoding, cbid, cbuserdata);
1459 char desired_section[64];
1466 * Callback function for
1468 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1469 void *content, char *cbtype, char *cbcharset, size_t length,
1470 char *encoding, char *cbid, void *cbuserdata)
1472 struct encapmsg *encap;
1474 encap = (struct encapmsg *)cbuserdata;
1476 /* Only proceed if this is the desired section... */
1477 if (!strcasecmp(encap->desired_section, partnum)) {
1478 encap->msglen = length;
1479 encap->msg = malloc(length + 2);
1480 memcpy(encap->msg, content, length);
1491 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1492 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1493 return(om_not_logged_in);
1500 * Get a message off disk. (returns om_* values found in msgbase.h)
1503 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1504 int mode, /* how would you like that message? */
1505 int headers_only, /* eschew the message body? */
1506 int do_proto, /* do Citadel protocol responses? */
1507 int crlf, /* Use CRLF newlines instead of LF? */
1508 char *section, /* NULL or a message/rfc822 section */
1509 int flags /* various flags; see msgbase.h */
1511 struct CtdlMessage *TheMessage = NULL;
1512 int retcode = om_no_such_msg;
1513 struct encapmsg encap;
1516 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1518 (section ? section : "<>")
1521 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1524 if (r == om_not_logged_in) {
1525 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1528 cprintf("%d An unknown error has occurred.\n", ERROR);
1534 /* FIXME: check message id against msglist for this room */
1537 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1538 * request that we don't even bother loading the body into memory.
1540 if (headers_only == HEADERS_FAST) {
1541 TheMessage = CtdlFetchMessage(msg_num, 0);
1544 TheMessage = CtdlFetchMessage(msg_num, 1);
1547 if (TheMessage == NULL) {
1548 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1549 ERROR + MESSAGE_NOT_FOUND, msg_num);
1550 return(om_no_such_msg);
1553 /* Here is the weird form of this command, to process only an
1554 * encapsulated message/rfc822 section.
1556 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1557 memset(&encap, 0, sizeof encap);
1558 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1559 mime_parser(TheMessage->cm_fields['M'],
1561 *extract_encapsulated_message,
1562 NULL, NULL, (void *)&encap, 0
1564 CtdlFreeMessage(TheMessage);
1568 encap.msg[encap.msglen] = 0;
1569 TheMessage = convert_internet_message(encap.msg);
1570 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1572 /* Now we let it fall through to the bottom of this
1573 * function, because TheMessage now contains the
1574 * encapsulated message instead of the top-level
1575 * message. Isn't that neat?
1580 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1581 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1582 retcode = om_no_such_msg;
1587 /* Ok, output the message now */
1588 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1589 CtdlFreeMessage(TheMessage);
1595 char *qp_encode_email_addrs(char *source)
1597 char user[256], node[256], name[256];
1598 const char headerStr[] = "=?UTF-8?Q?";
1602 int need_to_encode = 0;
1608 long nAddrPtrMax = 50;
1613 if (source == NULL) return source;
1614 if (IsEmptyStr(source)) return source;
1616 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1617 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1618 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1621 while (!IsEmptyStr (&source[i])) {
1622 if (nColons >= nAddrPtrMax){
1625 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1626 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1627 free (AddrPtr), AddrPtr = ptr;
1629 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1630 memset(&ptr[nAddrPtrMax], 0,
1631 sizeof (long) * nAddrPtrMax);
1633 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1634 free (AddrUtf8), AddrUtf8 = ptr;
1637 if (((unsigned char) source[i] < 32) ||
1638 ((unsigned char) source[i] > 126)) {
1640 AddrUtf8[nColons] = 1;
1642 if (source[i] == '"')
1643 InQuotes = !InQuotes;
1644 if (!InQuotes && source[i] == ',') {
1645 AddrPtr[nColons] = i;
1650 if (need_to_encode == 0) {
1657 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1658 Encoded = (char*) malloc (EncodedMaxLen);
1660 for (i = 0; i < nColons; i++)
1661 source[AddrPtr[i]++] = '\0';
1665 for (i = 0; i < nColons && nPtr != NULL; i++) {
1666 nmax = EncodedMaxLen - (nPtr - Encoded);
1668 process_rfc822_addr(&source[AddrPtr[i]],
1672 /* TODO: libIDN here ! */
1673 if (IsEmptyStr(name)) {
1674 n = snprintf(nPtr, nmax,
1675 (i==0)?"%s@%s" : ",%s@%s",
1679 EncodedName = rfc2047encode(name, strlen(name));
1680 n = snprintf(nPtr, nmax,
1681 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1682 EncodedName, user, node);
1687 n = snprintf(nPtr, nmax,
1688 (i==0)?"%s" : ",%s",
1689 &source[AddrPtr[i]]);
1695 ptr = (char*) malloc(EncodedMaxLen * 2);
1696 memcpy(ptr, Encoded, EncodedMaxLen);
1697 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1698 free(Encoded), Encoded = ptr;
1700 i--; /* do it once more with properly lengthened buffer */
1703 for (i = 0; i < nColons; i++)
1704 source[--AddrPtr[i]] = ',';
1711 /* If the last item in a list of recipients was truncated to a partial address,
1712 * remove it completely in order to avoid choking libSieve
1714 void sanitize_truncated_recipient(char *str)
1717 if (num_tokens(str, ',') < 2) return;
1719 int len = strlen(str);
1720 if (len < 900) return;
1721 if (len > 998) str[998] = 0;
1723 char *cptr = strrchr(str, ',');
1726 char *lptr = strchr(cptr, '<');
1727 char *rptr = strchr(cptr, '>');
1729 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1737 * Get a message off disk. (returns om_* values found in msgbase.h)
1739 int CtdlOutputPreLoadedMsg(
1740 struct CtdlMessage *TheMessage,
1741 int mode, /* how would you like that message? */
1742 int headers_only, /* eschew the message body? */
1743 int do_proto, /* do Citadel protocol responses? */
1744 int crlf, /* Use CRLF newlines instead of LF? */
1745 int flags /* should the bessage be exported clean? */
1749 cit_uint8_t ch, prev_ch;
1751 char display_name[256];
1753 const char *nl; /* newline string */
1755 int subject_found = 0;
1758 /* Buffers needed for RFC822 translation. These are all filled
1759 * using functions that are bounds-checked, and therefore we can
1760 * make them substantially smaller than SIZ.
1767 char datestamp[100];
1769 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1770 ((TheMessage == NULL) ? "NULL" : "not null"),
1771 mode, headers_only, do_proto, crlf);
1773 strcpy(mid, "unknown");
1774 nl = (crlf ? "\r\n" : "\n");
1776 if (!is_valid_message(TheMessage)) {
1777 CtdlLogPrintf(CTDL_ERR,
1778 "ERROR: invalid preloaded message for output\n");
1780 return(om_no_such_msg);
1783 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1784 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1786 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1787 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1790 /* Are we downloading a MIME component? */
1791 if (mode == MT_DOWNLOAD) {
1792 if (TheMessage->cm_format_type != FMT_RFC822) {
1794 cprintf("%d This is not a MIME message.\n",
1795 ERROR + ILLEGAL_VALUE);
1796 } else if (CC->download_fp != NULL) {
1797 if (do_proto) cprintf(
1798 "%d You already have a download open.\n",
1799 ERROR + RESOURCE_BUSY);
1801 /* Parse the message text component */
1802 mptr = TheMessage->cm_fields['M'];
1803 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1804 /* If there's no file open by this time, the requested
1805 * section wasn't found, so print an error
1807 if (CC->download_fp == NULL) {
1808 if (do_proto) cprintf(
1809 "%d Section %s not found.\n",
1810 ERROR + FILE_NOT_FOUND,
1811 CC->download_desired_section);
1814 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1817 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1818 * in a single server operation instead of opening a download file.
1820 if (mode == MT_SPEW_SECTION) {
1821 if (TheMessage->cm_format_type != FMT_RFC822) {
1823 cprintf("%d This is not a MIME message.\n",
1824 ERROR + ILLEGAL_VALUE);
1826 /* Parse the message text component */
1829 mptr = TheMessage->cm_fields['M'];
1830 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1831 /* If section wasn't found, print an error
1834 if (do_proto) cprintf(
1835 "%d Section %s not found.\n",
1836 ERROR + FILE_NOT_FOUND,
1837 CC->download_desired_section);
1840 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1843 /* now for the user-mode message reading loops */
1844 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1846 /* Does the caller want to skip the headers? */
1847 if (headers_only == HEADERS_NONE) goto START_TEXT;
1849 /* Tell the client which format type we're using. */
1850 if ( (mode == MT_CITADEL) && (do_proto) ) {
1851 cprintf("type=%d\n", TheMessage->cm_format_type);
1854 /* nhdr=yes means that we're only displaying headers, no body */
1855 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1856 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1859 cprintf("nhdr=yes\n");
1862 /* begin header processing loop for Citadel message format */
1864 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1866 safestrncpy(display_name, "<unknown>", sizeof display_name);
1867 if (TheMessage->cm_fields['A']) {
1868 strcpy(buf, TheMessage->cm_fields['A']);
1869 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1870 safestrncpy(display_name, "****", sizeof display_name);
1872 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1873 safestrncpy(display_name, "anonymous", sizeof display_name);
1876 safestrncpy(display_name, buf, sizeof display_name);
1878 if ((is_room_aide())
1879 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1880 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1881 size_t tmp = strlen(display_name);
1882 snprintf(&display_name[tmp],
1883 sizeof display_name - tmp,
1888 /* Don't show Internet address for users on the
1889 * local Citadel network.
1892 if (TheMessage->cm_fields['N'] != NULL)
1893 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1894 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1898 /* Now spew the header fields in the order we like them. */
1899 safestrncpy(allkeys, FORDER, sizeof allkeys);
1900 for (i=0; i<strlen(allkeys); ++i) {
1901 k = (int) allkeys[i];
1903 if ( (TheMessage->cm_fields[k] != NULL)
1904 && (msgkeys[k] != NULL) ) {
1905 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1906 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1909 if (do_proto) cprintf("%s=%s\n",
1913 else if ((k == 'F') && (suppress_f)) {
1916 /* Masquerade display name if needed */
1918 if (do_proto) cprintf("%s=%s\n",
1920 TheMessage->cm_fields[k]
1929 /* begin header processing loop for RFC822 transfer format */
1934 strcpy(snode, NODENAME);
1935 if (mode == MT_RFC822) {
1936 for (i = 0; i < 256; ++i) {
1937 if (TheMessage->cm_fields[i]) {
1938 mptr = mpptr = TheMessage->cm_fields[i];
1941 safestrncpy(luser, mptr, sizeof luser);
1942 safestrncpy(suser, mptr, sizeof suser);
1944 else if (i == 'Y') {
1945 if ((flags & QP_EADDR) != 0) {
1946 mptr = qp_encode_email_addrs(mptr);
1948 sanitize_truncated_recipient(mptr);
1949 cprintf("CC: %s%s", mptr, nl);
1951 else if (i == 'P') {
1952 cprintf("Return-Path: %s%s", mptr, nl);
1954 else if (i == 'L') {
1955 cprintf("List-ID: %s%s", mptr, nl);
1957 else if (i == 'V') {
1958 if ((flags & QP_EADDR) != 0)
1959 mptr = qp_encode_email_addrs(mptr);
1960 cprintf("Envelope-To: %s%s", mptr, nl);
1962 else if (i == 'U') {
1963 cprintf("Subject: %s%s", mptr, nl);
1967 safestrncpy(mid, mptr, sizeof mid);
1969 safestrncpy(fuser, mptr, sizeof fuser);
1970 /* else if (i == 'O')
1971 cprintf("X-Citadel-Room: %s%s",
1974 safestrncpy(snode, mptr, sizeof snode);
1977 if (haschar(mptr, '@') == 0)
1979 sanitize_truncated_recipient(mptr);
1980 cprintf("To: %s@%s", mptr, config.c_fqdn);
1985 if ((flags & QP_EADDR) != 0) {
1986 mptr = qp_encode_email_addrs(mptr);
1988 sanitize_truncated_recipient(mptr);
1989 cprintf("To: %s", mptr);
1993 else if (i == 'T') {
1994 datestring(datestamp, sizeof datestamp,
1995 atol(mptr), DATESTRING_RFC822);
1996 cprintf("Date: %s%s", datestamp, nl);
1998 else if (i == 'W') {
1999 cprintf("References: ");
2000 k = num_tokens(mptr, '|');
2001 for (j=0; j<k; ++j) {
2002 extract_token(buf, mptr, j, '|', sizeof buf);
2003 cprintf("<%s>", buf);
2016 if (subject_found == 0) {
2017 cprintf("Subject: (no subject)%s", nl);
2021 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2022 suser[i] = tolower(suser[i]);
2023 if (!isalnum(suser[i])) suser[i]='_';
2026 if (mode == MT_RFC822) {
2027 if (!strcasecmp(snode, NODENAME)) {
2028 safestrncpy(snode, FQDN, sizeof snode);
2031 /* Construct a fun message id */
2032 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2033 if (strchr(mid, '@')==NULL) {
2034 cprintf("@%s", snode);
2038 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2039 cprintf("From: \"----\" <x@x.org>%s", nl);
2041 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2042 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2044 else if (!IsEmptyStr(fuser)) {
2045 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2048 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2051 /* Blank line signifying RFC822 end-of-headers */
2052 if (TheMessage->cm_format_type != FMT_RFC822) {
2057 /* end header processing loop ... at this point, we're in the text */
2059 if (headers_only == HEADERS_FAST) goto DONE;
2060 mptr = TheMessage->cm_fields['M'];
2062 /* Tell the client about the MIME parts in this message */
2063 if (TheMessage->cm_format_type == FMT_RFC822) {
2064 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2065 memset(&ma, 0, sizeof(struct ma_info));
2066 mime_parser(mptr, NULL,
2067 (do_proto ? *list_this_part : NULL),
2068 (do_proto ? *list_this_pref : NULL),
2069 (do_proto ? *list_this_suff : NULL),
2072 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2073 char *start_of_text = NULL;
2074 start_of_text = strstr(mptr, "\n\r\n");
2075 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2076 if (start_of_text == NULL) start_of_text = mptr;
2078 start_of_text = strstr(start_of_text, "\n");
2083 int nllen = strlen(nl);
2085 while (ch=*mptr, ch!=0) {
2091 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2092 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2093 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2096 sprintf(&outbuf[outlen], "%s", nl);
2100 outbuf[outlen++] = ch;
2104 if (flags & ESC_DOT)
2106 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2108 outbuf[outlen++] = '.';
2113 if (outlen > 1000) {
2114 client_write(outbuf, outlen);
2119 client_write(outbuf, outlen);
2127 if (headers_only == HEADERS_ONLY) {
2131 /* signify start of msg text */
2132 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2133 if (do_proto) cprintf("text\n");
2136 /* If the format type on disk is 1 (fixed-format), then we want
2137 * everything to be output completely literally ... regardless of
2138 * what message transfer format is in use.
2140 if (TheMessage->cm_format_type == FMT_FIXED) {
2142 if (mode == MT_MIME) {
2143 cprintf("Content-type: text/plain\n\n");
2147 while (ch = *mptr++, ch > 0) {
2150 if ((ch == 10) || (buflen > 250)) {
2152 cprintf("%s%s", buf, nl);
2161 if (!IsEmptyStr(buf))
2162 cprintf("%s%s", buf, nl);
2165 /* If the message on disk is format 0 (Citadel vari-format), we
2166 * output using the formatter at 80 columns. This is the final output
2167 * form if the transfer format is RFC822, but if the transfer format
2168 * is Citadel proprietary, it'll still work, because the indentation
2169 * for new paragraphs is correct and the client will reformat the
2170 * message to the reader's screen width.
2172 if (TheMessage->cm_format_type == FMT_CITADEL) {
2173 if (mode == MT_MIME) {
2174 cprintf("Content-type: text/x-citadel-variformat\n\n");
2179 /* If the message on disk is format 4 (MIME), we've gotta hand it
2180 * off to the MIME parser. The client has already been told that
2181 * this message is format 1 (fixed format), so the callback function
2182 * we use will display those parts as-is.
2184 if (TheMessage->cm_format_type == FMT_RFC822) {
2185 memset(&ma, 0, sizeof(struct ma_info));
2187 if (mode == MT_MIME) {
2188 ma.use_fo_hooks = 0;
2189 strcpy(ma.chosen_part, "1");
2190 ma.chosen_pref = 9999;
2191 mime_parser(mptr, NULL,
2192 *choose_preferred, *fixed_output_pre,
2193 *fixed_output_post, (void *)&ma, 0);
2194 mime_parser(mptr, NULL,
2195 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2198 ma.use_fo_hooks = 1;
2199 mime_parser(mptr, NULL,
2200 *fixed_output, *fixed_output_pre,
2201 *fixed_output_post, (void *)&ma, 0);
2206 DONE: /* now we're done */
2207 if (do_proto) cprintf("000\n");
2214 * display a message (mode 0 - Citadel proprietary)
2216 void cmd_msg0(char *cmdbuf)
2219 int headers_only = HEADERS_ALL;
2221 msgid = extract_long(cmdbuf, 0);
2222 headers_only = extract_int(cmdbuf, 1);
2224 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2230 * display a message (mode 2 - RFC822)
2232 void cmd_msg2(char *cmdbuf)
2235 int headers_only = HEADERS_ALL;
2237 msgid = extract_long(cmdbuf, 0);
2238 headers_only = extract_int(cmdbuf, 1);
2240 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2246 * display a message (mode 3 - IGnet raw format - internal programs only)
2248 void cmd_msg3(char *cmdbuf)
2251 struct CtdlMessage *msg = NULL;
2254 if (CC->internal_pgm == 0) {
2255 cprintf("%d This command is for internal programs only.\n",
2256 ERROR + HIGHER_ACCESS_REQUIRED);
2260 msgnum = extract_long(cmdbuf, 0);
2261 msg = CtdlFetchMessage(msgnum, 1);
2263 cprintf("%d Message %ld not found.\n",
2264 ERROR + MESSAGE_NOT_FOUND, msgnum);
2268 serialize_message(&smr, msg);
2269 CtdlFreeMessage(msg);
2272 cprintf("%d Unable to serialize message\n",
2273 ERROR + INTERNAL_ERROR);
2277 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2278 client_write((char *)smr.ser, (int)smr.len);
2285 * Display a message using MIME content types
2287 void cmd_msg4(char *cmdbuf)
2292 msgid = extract_long(cmdbuf, 0);
2293 extract_token(section, cmdbuf, 1, '|', sizeof section);
2294 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2300 * Client tells us its preferred message format(s)
2302 void cmd_msgp(char *cmdbuf)
2304 if (!strcasecmp(cmdbuf, "dont_decode")) {
2305 CC->msg4_dont_decode = 1;
2306 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2309 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2310 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2316 * Open a component of a MIME message as a download file
2318 void cmd_opna(char *cmdbuf)
2321 char desired_section[128];
2323 msgid = extract_long(cmdbuf, 0);
2324 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2325 safestrncpy(CC->download_desired_section, desired_section,
2326 sizeof CC->download_desired_section);
2327 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2332 * Open a component of a MIME message and transmit it all at once
2334 void cmd_dlat(char *cmdbuf)
2337 char desired_section[128];
2339 msgid = extract_long(cmdbuf, 0);
2340 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2341 safestrncpy(CC->download_desired_section, desired_section,
2342 sizeof CC->download_desired_section);
2343 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2348 * Save one or more message pointers into a specified room
2349 * (Returns 0 for success, nonzero for failure)
2350 * roomname may be NULL to use the current room
2352 * Note that the 'supplied_msg' field may be set to NULL, in which case
2353 * the message will be fetched from disk, by number, if we need to perform
2354 * replication checks. This adds an additional database read, so if the
2355 * caller already has the message in memory then it should be supplied. (Obviously
2356 * this mode of operation only works if we're saving a single message.)
2358 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2359 int do_repl_check, struct CtdlMessage *supplied_msg)
2362 char hold_rm[ROOMNAMELEN];
2363 struct cdbdata *cdbfr;
2366 long highest_msg = 0L;
2369 struct CtdlMessage *msg = NULL;
2371 long *msgs_to_be_merged = NULL;
2372 int num_msgs_to_be_merged = 0;
2374 CtdlLogPrintf(CTDL_DEBUG,
2375 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2376 roomname, num_newmsgs, do_repl_check);
2378 strcpy(hold_rm, CC->room.QRname);
2381 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2382 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2383 if (num_newmsgs > 1) supplied_msg = NULL;
2385 /* Now the regular stuff */
2386 if (CtdlGetRoomLock(&CC->room,
2387 ((roomname != NULL) ? roomname : CC->room.QRname) )
2389 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2390 return(ERROR + ROOM_NOT_FOUND);
2394 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2395 num_msgs_to_be_merged = 0;
2398 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2399 if (cdbfr == NULL) {
2403 msglist = (long *) cdbfr->ptr;
2404 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2405 num_msgs = cdbfr->len / sizeof(long);
2410 /* Create a list of msgid's which were supplied by the caller, but do
2411 * not already exist in the target room. It is absolutely taboo to
2412 * have more than one reference to the same message in a room.
2414 for (i=0; i<num_newmsgs; ++i) {
2416 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2417 if (msglist[j] == newmsgidlist[i]) {
2422 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2426 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2429 * Now merge the new messages
2431 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2432 if (msglist == NULL) {
2433 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2435 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2436 num_msgs += num_msgs_to_be_merged;
2438 /* Sort the message list, so all the msgid's are in order */
2439 num_msgs = sort_msglist(msglist, num_msgs);
2441 /* Determine the highest message number */
2442 highest_msg = msglist[num_msgs - 1];
2444 /* Write it back to disk. */
2445 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2446 msglist, (int)(num_msgs * sizeof(long)));
2448 /* Free up the memory we used. */
2451 /* Update the highest-message pointer and unlock the room. */
2452 CC->room.QRhighest = highest_msg;
2453 CtdlPutRoomLock(&CC->room);
2455 /* Perform replication checks if necessary */
2456 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2457 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2459 for (i=0; i<num_msgs_to_be_merged; ++i) {
2460 msgid = msgs_to_be_merged[i];
2462 if (supplied_msg != NULL) {
2466 msg = CtdlFetchMessage(msgid, 0);
2470 ReplicationChecks(msg);
2472 /* If the message has an Exclusive ID, index that... */
2473 if (msg->cm_fields['E'] != NULL) {
2474 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2477 /* Free up the memory we may have allocated */
2478 if (msg != supplied_msg) {
2479 CtdlFreeMessage(msg);
2487 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2490 /* Submit this room for processing by hooks */
2491 PerformRoomHooks(&CC->room);
2493 /* Go back to the room we were in before we wandered here... */
2494 CtdlGetRoom(&CC->room, hold_rm);
2496 /* Bump the reference count for all messages which were merged */
2497 for (i=0; i<num_msgs_to_be_merged; ++i) {
2498 AdjRefCount(msgs_to_be_merged[i], +1);
2501 /* Free up memory... */
2502 if (msgs_to_be_merged != NULL) {
2503 free(msgs_to_be_merged);
2506 /* Return success. */
2512 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2515 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2516 int do_repl_check, struct CtdlMessage *supplied_msg)
2518 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2525 * Message base operation to save a new message to the message store
2526 * (returns new message number)
2528 * This is the back end for CtdlSubmitMsg() and should not be directly
2529 * called by server-side modules.
2532 long send_message(struct CtdlMessage *msg) {
2540 /* Get a new message number */
2541 newmsgid = get_new_message_number();
2542 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2544 /* Generate an ID if we don't have one already */
2545 if (msg->cm_fields['I']==NULL) {
2546 msg->cm_fields['I'] = strdup(msgidbuf);
2549 /* If the message is big, set its body aside for storage elsewhere */
2550 if (msg->cm_fields['M'] != NULL) {
2551 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2553 holdM = msg->cm_fields['M'];
2554 msg->cm_fields['M'] = NULL;
2558 /* Serialize our data structure for storage in the database */
2559 serialize_message(&smr, msg);
2562 msg->cm_fields['M'] = holdM;
2566 cprintf("%d Unable to serialize message\n",
2567 ERROR + INTERNAL_ERROR);
2571 /* Write our little bundle of joy into the message base */
2572 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2573 smr.ser, smr.len) < 0) {
2574 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2578 cdb_store(CDB_BIGMSGS,
2588 /* Free the memory we used for the serialized message */
2591 /* Return the *local* message ID to the caller
2592 * (even if we're storing an incoming network message)
2600 * Serialize a struct CtdlMessage into the format used on disk and network.
2602 * This function loads up a "struct ser_ret" (defined in server.h) which
2603 * contains the length of the serialized message and a pointer to the
2604 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2606 void serialize_message(struct ser_ret *ret, /* return values */
2607 struct CtdlMessage *msg) /* unserialized msg */
2609 size_t wlen, fieldlen;
2611 static char *forder = FORDER;
2614 * Check for valid message format
2616 if (is_valid_message(msg) == 0) {
2617 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2624 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2625 ret->len = ret->len +
2626 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2628 ret->ser = malloc(ret->len);
2629 if (ret->ser == NULL) {
2630 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2631 (long)ret->len, strerror(errno));
2638 ret->ser[1] = msg->cm_anon_type;
2639 ret->ser[2] = msg->cm_format_type;
2642 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2643 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2644 ret->ser[wlen++] = (char)forder[i];
2645 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2646 wlen = wlen + fieldlen + 1;
2648 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2649 (long)ret->len, (long)wlen);
2656 * Serialize a struct CtdlMessage into the format used on disk and network.
2658 * This function loads up a "struct ser_ret" (defined in server.h) which
2659 * contains the length of the serialized message and a pointer to the
2660 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2662 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2663 long Siz) /* how many chars ? */
2667 static char *forder = FORDER;
2671 * Check for valid message format
2673 if (is_valid_message(msg) == 0) {
2674 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2678 buf = (char*) malloc (Siz + 1);
2682 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2683 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2684 msg->cm_fields[(int)forder[i]]);
2685 client_write (buf, strlen(buf));
2694 * Check to see if any messages already exist in the current room which
2695 * carry the same Exclusive ID as this one. If any are found, delete them.
2697 void ReplicationChecks(struct CtdlMessage *msg) {
2698 long old_msgnum = (-1L);
2700 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2702 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2705 /* No exclusive id? Don't do anything. */
2706 if (msg == NULL) return;
2707 if (msg->cm_fields['E'] == NULL) return;
2708 if (IsEmptyStr(msg->cm_fields['E'])) return;
2709 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2710 msg->cm_fields['E'], CC->room.QRname);*/
2712 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2713 if (old_msgnum > 0L) {
2714 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2715 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2722 * Save a message to disk and submit it into the delivery system.
2724 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2725 struct recptypes *recps, /* recipients (if mail) */
2726 char *force, /* force a particular room? */
2727 int flags /* should the message be exported clean? */
2729 char submit_filename[128];
2730 char generated_timestamp[32];
2731 char hold_rm[ROOMNAMELEN];
2732 char actual_rm[ROOMNAMELEN];
2733 char force_room[ROOMNAMELEN];
2734 char content_type[SIZ]; /* We have to learn this */
2735 char recipient[SIZ];
2738 struct ctdluser userbuf;
2740 struct MetaData smi;
2741 FILE *network_fp = NULL;
2742 static int seqnum = 1;
2743 struct CtdlMessage *imsg = NULL;
2745 size_t instr_alloc = 0;
2747 char *hold_R, *hold_D;
2748 char *collected_addresses = NULL;
2749 struct addresses_to_be_filed *aptr = NULL;
2750 char *saved_rfc822_version = NULL;
2751 int qualified_for_journaling = 0;
2752 CitContext *CCC = CC; /* CachedCitContext - performance boost */
2753 char bounce_to[1024] = "";
2757 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2758 if (is_valid_message(msg) == 0) return(-1); /* self check */
2760 /* If this message has no timestamp, we take the liberty of
2761 * giving it one, right now.
2763 if (msg->cm_fields['T'] == NULL) {
2764 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2765 msg->cm_fields['T'] = strdup(generated_timestamp);
2768 /* If this message has no path, we generate one.
2770 if (msg->cm_fields['P'] == NULL) {
2771 if (msg->cm_fields['A'] != NULL) {
2772 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2773 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2774 if (isspace(msg->cm_fields['P'][a])) {
2775 msg->cm_fields['P'][a] = ' ';
2780 msg->cm_fields['P'] = strdup("unknown");
2784 if (force == NULL) {
2785 strcpy(force_room, "");
2788 strcpy(force_room, force);
2791 /* Learn about what's inside, because it's what's inside that counts */
2792 if (msg->cm_fields['M'] == NULL) {
2793 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2797 switch (msg->cm_format_type) {
2799 strcpy(content_type, "text/x-citadel-variformat");
2802 strcpy(content_type, "text/plain");
2805 strcpy(content_type, "text/plain");
2806 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2809 safestrncpy(content_type, &mptr[13], sizeof content_type);
2810 striplt(content_type);
2811 aptr = content_type;
2812 while (!IsEmptyStr(aptr)) {
2824 /* Goto the correct room */
2825 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2826 strcpy(hold_rm, CCC->room.QRname);
2827 strcpy(actual_rm, CCC->room.QRname);
2828 if (recps != NULL) {
2829 strcpy(actual_rm, SENTITEMS);
2832 /* If the user is a twit, move to the twit room for posting */
2834 if (CCC->user.axlevel == 2) {
2835 strcpy(hold_rm, actual_rm);
2836 strcpy(actual_rm, config.c_twitroom);
2837 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2841 /* ...or if this message is destined for Aide> then go there. */
2842 if (!IsEmptyStr(force_room)) {
2843 strcpy(actual_rm, force_room);
2846 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2847 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2848 /* CtdlGetRoom(&CCC->room, actual_rm); */
2849 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2853 * If this message has no O (room) field, generate one.
2855 if (msg->cm_fields['O'] == NULL) {
2856 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2859 /* Perform "before save" hooks (aborting if any return nonzero) */
2860 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2861 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2864 * If this message has an Exclusive ID, and the room is replication
2865 * checking enabled, then do replication checks.
2867 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2868 ReplicationChecks(msg);
2871 /* Save it to disk */
2872 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2873 newmsgid = send_message(msg);
2874 if (newmsgid <= 0L) return(-5);
2876 /* Write a supplemental message info record. This doesn't have to
2877 * be a critical section because nobody else knows about this message
2880 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2881 memset(&smi, 0, sizeof(struct MetaData));
2882 smi.meta_msgnum = newmsgid;
2883 smi.meta_refcount = 0;
2884 safestrncpy(smi.meta_content_type, content_type,
2885 sizeof smi.meta_content_type);
2888 * Measure how big this message will be when rendered as RFC822.
2889 * We do this for two reasons:
2890 * 1. We need the RFC822 length for the new metadata record, so the
2891 * POP and IMAP services don't have to calculate message lengths
2892 * while the user is waiting (multiplied by potentially hundreds
2893 * or thousands of messages).
2894 * 2. If journaling is enabled, we will need an RFC822 version of the
2895 * message to attach to the journalized copy.
2897 if (CCC->redirect_buffer != NULL) {
2898 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2901 CCC->redirect_buffer = malloc(SIZ);
2902 CCC->redirect_len = 0;
2903 CCC->redirect_alloc = SIZ;
2904 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2905 smi.meta_rfc822_length = CCC->redirect_len;
2906 saved_rfc822_version = CCC->redirect_buffer;
2907 CCC->redirect_buffer = NULL;
2908 CCC->redirect_len = 0;
2909 CCC->redirect_alloc = 0;
2913 /* Now figure out where to store the pointers */
2914 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2916 /* If this is being done by the networker delivering a private
2917 * message, we want to BYPASS saving the sender's copy (because there
2918 * is no local sender; it would otherwise go to the Trashcan).
2920 if ((!CCC->internal_pgm) || (recps == NULL)) {
2921 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2922 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2923 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2927 /* For internet mail, drop a copy in the outbound queue room */
2928 if ((recps != NULL) && (recps->num_internet > 0)) {
2929 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2932 /* If other rooms are specified, drop them there too. */
2933 if ((recps != NULL) && (recps->num_room > 0))
2934 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2935 extract_token(recipient, recps->recp_room, i,
2936 '|', sizeof recipient);
2937 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2938 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2941 /* Bump this user's messages posted counter. */
2942 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2943 CtdlGetUserLock(&CCC->user, CCC->curr_user);
2944 CCC->user.posted = CCC->user.posted + 1;
2945 CtdlPutUserLock(&CCC->user);
2947 /* Decide where bounces need to be delivered */
2948 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2949 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2951 else if (CCC->logged_in) {
2952 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2955 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2958 /* If this is private, local mail, make a copy in the
2959 * recipient's mailbox and bump the reference count.
2961 if ((recps != NULL) && (recps->num_local > 0))
2962 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2963 extract_token(recipient, recps->recp_local, i,
2964 '|', sizeof recipient);
2965 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2967 if (CtdlGetUser(&userbuf, recipient) == 0) {
2968 // Add a flag so the Funambol module knows its mail
2969 msg->cm_fields['W'] = strdup(recipient);
2970 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2971 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2972 CtdlBumpNewMailCounter(userbuf.usernum);
2973 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2974 /* Generate a instruction message for the Funambol notification
2975 * server, in the same style as the SMTP queue
2978 instr = malloc(instr_alloc);
2979 snprintf(instr, instr_alloc,
2980 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2982 SPOOLMIME, newmsgid, (long)time(NULL),
2986 imsg = malloc(sizeof(struct CtdlMessage));
2987 memset(imsg, 0, sizeof(struct CtdlMessage));
2988 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2989 imsg->cm_anon_type = MES_NORMAL;
2990 imsg->cm_format_type = FMT_RFC822;
2991 imsg->cm_fields['A'] = strdup("Citadel");
2992 imsg->cm_fields['J'] = strdup("do not journal");
2993 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2994 imsg->cm_fields['W'] = strdup(recipient);
2995 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2996 CtdlFreeMessage(imsg);
3000 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3001 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3006 /* Perform "after save" hooks */
3007 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3008 PerformMessageHooks(msg, EVT_AFTERSAVE);
3010 /* For IGnet mail, we have to save a new copy into the spooler for
3011 * each recipient, with the R and D fields set to the recipient and
3012 * destination-node. This has two ugly side effects: all other
3013 * recipients end up being unlisted in this recipient's copy of the
3014 * message, and it has to deliver multiple messages to the same
3015 * node. We'll revisit this again in a year or so when everyone has
3016 * a network spool receiver that can handle the new style messages.
3018 if ((recps != NULL) && (recps->num_ignet > 0))
3019 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3020 extract_token(recipient, recps->recp_ignet, i,
3021 '|', sizeof recipient);
3023 hold_R = msg->cm_fields['R'];
3024 hold_D = msg->cm_fields['D'];
3025 msg->cm_fields['R'] = malloc(SIZ);
3026 msg->cm_fields['D'] = malloc(128);
3027 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3028 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3030 serialize_message(&smr, msg);
3032 snprintf(submit_filename, sizeof submit_filename,
3033 "%s/netmail.%04lx.%04x.%04x",
3035 (long) getpid(), CCC->cs_pid, ++seqnum);
3036 network_fp = fopen(submit_filename, "wb+");
3037 if (network_fp != NULL) {
3038 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3044 free(msg->cm_fields['R']);
3045 free(msg->cm_fields['D']);
3046 msg->cm_fields['R'] = hold_R;
3047 msg->cm_fields['D'] = hold_D;
3050 /* Go back to the room we started from */
3051 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3052 if (strcasecmp(hold_rm, CCC->room.QRname))
3053 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3055 /* For internet mail, generate delivery instructions.
3056 * Yes, this is recursive. Deal with it. Infinite recursion does
3057 * not happen because the delivery instructions message does not
3058 * contain a recipient.
3060 if ((recps != NULL) && (recps->num_internet > 0)) {
3061 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3063 instr = malloc(instr_alloc);
3064 snprintf(instr, instr_alloc,
3065 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3067 SPOOLMIME, newmsgid, (long)time(NULL),
3071 if (recps->envelope_from != NULL) {
3072 tmp = strlen(instr);
3073 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3076 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3077 tmp = strlen(instr);
3078 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3079 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3080 instr_alloc = instr_alloc * 2;
3081 instr = realloc(instr, instr_alloc);
3083 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3086 imsg = malloc(sizeof(struct CtdlMessage));
3087 memset(imsg, 0, sizeof(struct CtdlMessage));
3088 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3089 imsg->cm_anon_type = MES_NORMAL;
3090 imsg->cm_format_type = FMT_RFC822;
3091 imsg->cm_fields['A'] = strdup("Citadel");
3092 imsg->cm_fields['J'] = strdup("do not journal");
3093 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3094 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3095 CtdlFreeMessage(imsg);
3099 * Any addresses to harvest for someone's address book?
3101 if ( (CCC->logged_in) && (recps != NULL) ) {
3102 collected_addresses = harvest_collected_addresses(msg);
3105 if (collected_addresses != NULL) {
3106 aptr = (struct addresses_to_be_filed *)
3107 malloc(sizeof(struct addresses_to_be_filed));
3108 CtdlMailboxName(actual_rm, sizeof actual_rm,
3109 &CCC->user, USERCONTACTSROOM);
3110 aptr->roomname = strdup(actual_rm);
3111 aptr->collected_addresses = collected_addresses;
3112 begin_critical_section(S_ATBF);
3115 end_critical_section(S_ATBF);
3119 * Determine whether this message qualifies for journaling.
3121 if (msg->cm_fields['J'] != NULL) {
3122 qualified_for_journaling = 0;
3125 if (recps == NULL) {
3126 qualified_for_journaling = config.c_journal_pubmsgs;
3128 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3129 qualified_for_journaling = config.c_journal_email;
3132 qualified_for_journaling = config.c_journal_pubmsgs;
3137 * Do we have to perform journaling? If so, hand off the saved
3138 * RFC822 version will be handed off to the journaler for background
3139 * submit. Otherwise, we have to free the memory ourselves.
3141 if (saved_rfc822_version != NULL) {
3142 if (qualified_for_journaling) {
3143 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3146 free(saved_rfc822_version);
3156 void aide_message (char *text, char *subject)
3158 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3163 * Convenience function for generating small administrative messages.
3165 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3166 int format_type, const char *subject)
3168 struct CtdlMessage *msg;
3169 struct recptypes *recp = NULL;
3171 msg = malloc(sizeof(struct CtdlMessage));
3172 memset(msg, 0, sizeof(struct CtdlMessage));
3173 msg->cm_magic = CTDLMESSAGE_MAGIC;
3174 msg->cm_anon_type = MES_NORMAL;
3175 msg->cm_format_type = format_type;
3178 msg->cm_fields['A'] = strdup(from);
3180 else if (fromaddr != NULL) {
3181 msg->cm_fields['A'] = strdup(fromaddr);
3182 if (strchr(msg->cm_fields['A'], '@')) {
3183 *strchr(msg->cm_fields['A'], '@') = 0;
3187 msg->cm_fields['A'] = strdup("Citadel");
3190 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3191 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3192 msg->cm_fields['N'] = strdup(NODENAME);
3194 msg->cm_fields['R'] = strdup(to);
3195 recp = validate_recipients(to, NULL, 0);
3197 if (subject != NULL) {
3198 msg->cm_fields['U'] = strdup(subject);
3200 msg->cm_fields['M'] = strdup(text);
3202 CtdlSubmitMsg(msg, recp, room, 0);
3203 CtdlFreeMessage(msg);
3204 if (recp != NULL) free_recipients(recp);
3210 * Back end function used by CtdlMakeMessage() and similar functions
3212 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3213 size_t maxlen, /* maximum message length */
3214 char *exist, /* if non-null, append to it;
3215 exist is ALWAYS freed */
3216 int crlf, /* CRLF newlines instead of LF */
3217 int sock /* socket handle or 0 for this session's client socket */
3221 size_t message_len = 0;
3222 size_t buffer_len = 0;
3229 if (exist == NULL) {
3236 message_len = strlen(exist);
3237 buffer_len = message_len + 4096;
3238 m = realloc(exist, buffer_len);
3245 /* Do we need to change leading ".." to "." for SMTP escaping? */
3246 if (!strcmp(terminator, ".")) {
3250 /* flush the input if we have nowhere to store it */
3255 /* read in the lines of message text one by one */
3258 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3261 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3263 if (!strcmp(buf, terminator)) finished = 1;
3265 strcat(buf, "\r\n");
3271 /* Unescape SMTP-style input of two dots at the beginning of the line */
3273 if (!strncmp(buf, "..", 2)) {
3274 strcpy(buf, &buf[1]);
3278 if ( (!flushing) && (!finished) ) {
3279 /* Measure the line */
3280 linelen = strlen(buf);
3282 /* augment the buffer if we have to */
3283 if ((message_len + linelen) >= buffer_len) {
3284 ptr = realloc(m, (buffer_len * 2) );
3285 if (ptr == NULL) { /* flush if can't allocate */
3288 buffer_len = (buffer_len * 2);
3290 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3294 /* Add the new line to the buffer. NOTE: this loop must avoid
3295 * using functions like strcat() and strlen() because they
3296 * traverse the entire buffer upon every call, and doing that
3297 * for a multi-megabyte message slows it down beyond usability.
3299 strcpy(&m[message_len], buf);
3300 message_len += linelen;
3303 /* if we've hit the max msg length, flush the rest */
3304 if (message_len >= maxlen) flushing = 1;
3306 } while (!finished);
3314 * Build a binary message to be saved on disk.
3315 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3316 * will become part of the message. This means you are no longer
3317 * responsible for managing that memory -- it will be freed along with
3318 * the rest of the fields when CtdlFreeMessage() is called.)
3321 struct CtdlMessage *CtdlMakeMessage(
3322 struct ctdluser *author, /* author's user structure */
3323 char *recipient, /* NULL if it's not mail */
3324 char *recp_cc, /* NULL if it's not mail */
3325 char *room, /* room where it's going */
3326 int type, /* see MES_ types in header file */
3327 int format_type, /* variformat, plain text, MIME... */
3328 char *fake_name, /* who we're masquerading as */
3329 char *my_email, /* which of my email addresses to use (empty is ok) */
3330 char *subject, /* Subject (optional) */
3331 char *supplied_euid, /* ...or NULL if this is irrelevant */
3332 char *preformatted_text, /* ...or NULL to read text from client */
3333 char *references /* Thread references */
3335 char dest_node[256];
3337 struct CtdlMessage *msg;
3339 msg = malloc(sizeof(struct CtdlMessage));
3340 memset(msg, 0, sizeof(struct CtdlMessage));
3341 msg->cm_magic = CTDLMESSAGE_MAGIC;
3342 msg->cm_anon_type = type;
3343 msg->cm_format_type = format_type;
3345 /* Don't confuse the poor folks if it's not routed mail. */
3346 strcpy(dest_node, "");
3348 if (recipient != NULL) striplt(recipient);
3349 if (recp_cc != NULL) striplt(recp_cc);
3351 /* Path or Return-Path */
3352 if (my_email == NULL) my_email = "";
3354 if (!IsEmptyStr(my_email)) {
3355 msg->cm_fields['P'] = strdup(my_email);
3358 snprintf(buf, sizeof buf, "%s", author->fullname);
3359 msg->cm_fields['P'] = strdup(buf);
3361 convert_spaces_to_underscores(msg->cm_fields['P']);
3363 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3364 msg->cm_fields['T'] = strdup(buf);
3366 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3367 msg->cm_fields['A'] = strdup(fake_name);
3370 msg->cm_fields['A'] = strdup(author->fullname);
3373 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3374 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3377 msg->cm_fields['O'] = strdup(CC->room.QRname);
3380 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3381 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3383 if ((recipient != NULL) && (recipient[0] != 0)) {
3384 msg->cm_fields['R'] = strdup(recipient);
3386 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3387 msg->cm_fields['Y'] = strdup(recp_cc);
3389 if (dest_node[0] != 0) {
3390 msg->cm_fields['D'] = strdup(dest_node);
3393 if (!IsEmptyStr(my_email)) {
3394 msg->cm_fields['F'] = strdup(my_email);
3396 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3397 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3400 if (subject != NULL) {
3403 length = strlen(subject);
3409 while ((subject[i] != '\0') &&
3410 (IsAscii = isascii(subject[i]) != 0 ))
3413 msg->cm_fields['U'] = strdup(subject);
3414 else /* ok, we've got utf8 in the string. */
3416 msg->cm_fields['U'] = rfc2047encode(subject, length);
3422 if (supplied_euid != NULL) {
3423 msg->cm_fields['E'] = strdup(supplied_euid);
3426 if (references != NULL) {
3427 if (!IsEmptyStr(references)) {
3428 msg->cm_fields['W'] = strdup(references);
3432 if (preformatted_text != NULL) {
3433 msg->cm_fields['M'] = preformatted_text;
3436 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3444 * Check to see whether we have permission to post a message in the current
3445 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3446 * returns 0 on success.
3448 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3450 const char* RemoteIdentifier,
3454 if (!(CC->logged_in) &&
3455 (PostPublic == POST_LOGGED_IN)) {
3456 snprintf(errmsgbuf, n, "Not logged in.");
3457 return (ERROR + NOT_LOGGED_IN);
3459 else if (PostPublic == CHECK_EXISTANCE) {
3460 return (0); // We're Evaling whether a recipient exists
3462 else if (!(CC->logged_in)) {
3464 if ((CC->room.QRflags & QR_READONLY)) {
3465 snprintf(errmsgbuf, n, "Not logged in.");
3466 return (ERROR + NOT_LOGGED_IN);
3468 if (CC->room.QRflags2 & QR2_MODERATED) {
3469 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3470 return (ERROR + NOT_LOGGED_IN);
3472 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3477 if (RemoteIdentifier == NULL)
3479 snprintf(errmsgbuf, n, "Need sender to permit access.");
3480 return (ERROR + USERNAME_REQUIRED);
3483 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3484 begin_critical_section(S_NETCONFIGS);
3485 if (!read_spoolcontrol_file(&sc, filename))
3487 end_critical_section(S_NETCONFIGS);
3488 snprintf(errmsgbuf, n,
3489 "This mailing list only accepts posts from subscribers.");
3490 return (ERROR + NO_SUCH_USER);
3492 end_critical_section(S_NETCONFIGS);
3493 found = is_recipient (sc, RemoteIdentifier);
3494 free_spoolcontrol_struct(&sc);
3499 snprintf(errmsgbuf, n,
3500 "This mailing list only accepts posts from subscribers.");
3501 return (ERROR + NO_SUCH_USER);
3508 if ((CC->user.axlevel < 2)
3509 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3510 snprintf(errmsgbuf, n, "Need to be validated to enter "
3511 "(except in %s> to sysop)", MAILROOM);
3512 return (ERROR + HIGHER_ACCESS_REQUIRED);
3515 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3516 if (!(ra & UA_POSTALLOWED)) {
3517 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3518 return (ERROR + HIGHER_ACCESS_REQUIRED);
3521 strcpy(errmsgbuf, "Ok");
3527 * Check to see if the specified user has Internet mail permission
3528 * (returns nonzero if permission is granted)
3530 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3532 /* Do not allow twits to send Internet mail */
3533 if (who->axlevel <= 2) return(0);
3535 /* Globally enabled? */
3536 if (config.c_restrict == 0) return(1);
3538 /* User flagged ok? */
3539 if (who->flags & US_INTERNET) return(2);
3541 /* Aide level access? */
3542 if (who->axlevel >= 6) return(3);
3544 /* No mail for you! */
3550 * Validate recipients, count delivery types and errors, and handle aliasing
3551 * FIXME check for dupes!!!!!
3553 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3554 * were specified, or the number of addresses found invalid.
3556 * Caller needs to free the result using free_recipients()
3558 struct recptypes *validate_recipients(char *supplied_recipients,
3559 const char *RemoteIdentifier,
3561 struct recptypes *ret;
3562 char *recipients = NULL;
3563 char this_recp[256];
3564 char this_recp_cooked[256];
3570 struct ctdluser tempUS;
3571 struct ctdlroom tempQR;
3572 struct ctdlroom tempQR2;
3578 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3579 if (ret == NULL) return(NULL);
3581 /* Set all strings to null and numeric values to zero */
3582 memset(ret, 0, sizeof(struct recptypes));
3584 if (supplied_recipients == NULL) {
3585 recipients = strdup("");
3588 recipients = strdup(supplied_recipients);
3591 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3592 * actually need, but it's healthier for the heap than doing lots of tiny
3593 * realloc() calls instead.
3596 ret->errormsg = malloc(strlen(recipients) + 1024);
3597 ret->recp_local = malloc(strlen(recipients) + 1024);
3598 ret->recp_internet = malloc(strlen(recipients) + 1024);
3599 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3600 ret->recp_room = malloc(strlen(recipients) + 1024);
3601 ret->display_recp = malloc(strlen(recipients) + 1024);
3603 ret->errormsg[0] = 0;
3604 ret->recp_local[0] = 0;
3605 ret->recp_internet[0] = 0;
3606 ret->recp_ignet[0] = 0;
3607 ret->recp_room[0] = 0;
3608 ret->display_recp[0] = 0;
3610 ret->recptypes_magic = RECPTYPES_MAGIC;
3612 /* Change all valid separator characters to commas */
3613 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3614 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3615 recipients[i] = ',';
3619 /* Now start extracting recipients... */
3621 while (!IsEmptyStr(recipients)) {
3623 for (i=0; i<=strlen(recipients); ++i) {
3624 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3625 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3626 safestrncpy(this_recp, recipients, i+1);
3628 if (recipients[i] == ',') {
3629 strcpy(recipients, &recipients[i+1]);
3632 strcpy(recipients, "");
3639 if (IsEmptyStr(this_recp))
3641 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3643 mailtype = alias(this_recp);
3644 mailtype = alias(this_recp);
3645 mailtype = alias(this_recp);
3647 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3648 if (this_recp[j]=='_') {
3649 this_recp_cooked[j] = ' ';
3652 this_recp_cooked[j] = this_recp[j];
3655 this_recp_cooked[j] = '\0';
3660 if (!strcasecmp(this_recp, "sysop")) {
3662 strcpy(this_recp, config.c_aideroom);
3663 if (!IsEmptyStr(ret->recp_room)) {
3664 strcat(ret->recp_room, "|");
3666 strcat(ret->recp_room, this_recp);
3668 else if ( (!strncasecmp(this_recp, "room_", 5))
3669 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3671 /* Save room so we can restore it later */
3675 /* Check permissions to send mail to this room */
3676 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3688 if (!IsEmptyStr(ret->recp_room)) {
3689 strcat(ret->recp_room, "|");
3691 strcat(ret->recp_room, &this_recp_cooked[5]);
3694 /* Restore room in case something needs it */
3698 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3700 strcpy(this_recp, tempUS.fullname);
3701 if (!IsEmptyStr(ret->recp_local)) {
3702 strcat(ret->recp_local, "|");
3704 strcat(ret->recp_local, this_recp);
3706 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3708 strcpy(this_recp, tempUS.fullname);
3709 if (!IsEmptyStr(ret->recp_local)) {
3710 strcat(ret->recp_local, "|");
3712 strcat(ret->recp_local, this_recp);
3720 /* Yes, you're reading this correctly: if the target
3721 * domain points back to the local system or an attached
3722 * Citadel directory, the address is invalid. That's
3723 * because if the address were valid, we would have
3724 * already translated it to a local address by now.
3726 if (IsDirectory(this_recp, 0)) {
3731 ++ret->num_internet;
3732 if (!IsEmptyStr(ret->recp_internet)) {
3733 strcat(ret->recp_internet, "|");
3735 strcat(ret->recp_internet, this_recp);
3740 if (!IsEmptyStr(ret->recp_ignet)) {
3741 strcat(ret->recp_ignet, "|");
3743 strcat(ret->recp_ignet, this_recp);
3751 if (IsEmptyStr(errmsg)) {
3752 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3755 snprintf(append, sizeof append, "%s", errmsg);
3757 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3758 if (!IsEmptyStr(ret->errormsg)) {
3759 strcat(ret->errormsg, "; ");
3761 strcat(ret->errormsg, append);
3765 if (IsEmptyStr(ret->display_recp)) {
3766 strcpy(append, this_recp);
3769 snprintf(append, sizeof append, ", %s", this_recp);
3771 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3772 strcat(ret->display_recp, append);
3777 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3778 ret->num_room + ret->num_error) == 0) {
3779 ret->num_error = (-1);
3780 strcpy(ret->errormsg, "No recipients specified.");
3783 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3784 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3785 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3786 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3787 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3788 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3796 * Destructor for struct recptypes
3798 void free_recipients(struct recptypes *valid) {
3800 if (valid == NULL) {
3804 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3805 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3809 if (valid->errormsg != NULL) free(valid->errormsg);
3810 if (valid->recp_local != NULL) free(valid->recp_local);
3811 if (valid->recp_internet != NULL) free(valid->recp_internet);
3812 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3813 if (valid->recp_room != NULL) free(valid->recp_room);
3814 if (valid->display_recp != NULL) free(valid->display_recp);
3815 if (valid->bounce_to != NULL) free(valid->bounce_to);
3816 if (valid->envelope_from != NULL) free(valid->envelope_from);
3823 * message entry - mode 0 (normal)
3825 void cmd_ent0(char *entargs)
3831 char supplied_euid[128];
3833 int format_type = 0;
3834 char newusername[256];
3835 char newuseremail[256];
3836 struct CtdlMessage *msg;
3840 struct recptypes *valid = NULL;
3841 struct recptypes *valid_to = NULL;
3842 struct recptypes *valid_cc = NULL;
3843 struct recptypes *valid_bcc = NULL;
3845 int subject_required = 0;
3850 int newuseremail_ok = 0;
3851 char references[SIZ];
3856 post = extract_int(entargs, 0);
3857 extract_token(recp, entargs, 1, '|', sizeof recp);
3858 anon_flag = extract_int(entargs, 2);
3859 format_type = extract_int(entargs, 3);
3860 extract_token(subject, entargs, 4, '|', sizeof subject);
3861 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3862 do_confirm = extract_int(entargs, 6);
3863 extract_token(cc, entargs, 7, '|', sizeof cc);
3864 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3865 switch(CC->room.QRdefaultview) {
3868 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3871 supplied_euid[0] = 0;
3874 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3875 extract_token(references, entargs, 11, '|', sizeof references);
3876 for (ptr=references; *ptr != 0; ++ptr) {
3877 if (*ptr == '!') *ptr = '|';
3880 /* first check to make sure the request is valid. */
3882 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3885 cprintf("%d %s\n", err, errmsg);
3889 /* Check some other permission type things. */
3891 if (IsEmptyStr(newusername)) {
3892 strcpy(newusername, CC->user.fullname);
3894 if ( (CC->user.axlevel < 6)
3895 && (strcasecmp(newusername, CC->user.fullname))
3896 && (strcasecmp(newusername, CC->cs_inet_fn))
3898 cprintf("%d You don't have permission to author messages as '%s'.\n",
3899 ERROR + HIGHER_ACCESS_REQUIRED,
3906 if (IsEmptyStr(newuseremail)) {
3907 newuseremail_ok = 1;
3910 if (!IsEmptyStr(newuseremail)) {
3911 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3912 newuseremail_ok = 1;
3914 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3915 j = num_tokens(CC->cs_inet_other_emails, '|');
3916 for (i=0; i<j; ++i) {
3917 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3918 if (!strcasecmp(newuseremail, buf)) {
3919 newuseremail_ok = 1;
3925 if (!newuseremail_ok) {
3926 cprintf("%d You don't have permission to author messages as '%s'.\n",
3927 ERROR + HIGHER_ACCESS_REQUIRED,
3933 CC->cs_flags |= CS_POSTING;
3935 /* In mailbox rooms we have to behave a little differently --
3936 * make sure the user has specified at least one recipient. Then
3937 * validate the recipient(s). We do this for the Mail> room, as
3938 * well as any room which has the "Mailbox" view set.
3941 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3942 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3944 if (CC->user.axlevel < 2) {
3945 strcpy(recp, "sysop");
3950 valid_to = validate_recipients(recp, NULL, 0);
3951 if (valid_to->num_error > 0) {
3952 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3953 free_recipients(valid_to);
3957 valid_cc = validate_recipients(cc, NULL, 0);
3958 if (valid_cc->num_error > 0) {
3959 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3960 free_recipients(valid_to);
3961 free_recipients(valid_cc);
3965 valid_bcc = validate_recipients(bcc, NULL, 0);
3966 if (valid_bcc->num_error > 0) {
3967 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3968 free_recipients(valid_to);
3969 free_recipients(valid_cc);
3970 free_recipients(valid_bcc);
3974 /* Recipient required, but none were specified */
3975 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3976 free_recipients(valid_to);
3977 free_recipients(valid_cc);
3978 free_recipients(valid_bcc);
3979 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3983 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3984 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3985 cprintf("%d You do not have permission "
3986 "to send Internet mail.\n",
3987 ERROR + HIGHER_ACCESS_REQUIRED);
3988 free_recipients(valid_to);
3989 free_recipients(valid_cc);
3990 free_recipients(valid_bcc);
3995 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)
3996 && (CC->user.axlevel < 4) ) {
3997 cprintf("%d Higher access required for network mail.\n",
3998 ERROR + HIGHER_ACCESS_REQUIRED);
3999 free_recipients(valid_to);
4000 free_recipients(valid_cc);
4001 free_recipients(valid_bcc);
4005 if ((RESTRICT_INTERNET == 1)
4006 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4007 && ((CC->user.flags & US_INTERNET) == 0)
4008 && (!CC->internal_pgm)) {
4009 cprintf("%d You don't have access to Internet mail.\n",
4010 ERROR + HIGHER_ACCESS_REQUIRED);
4011 free_recipients(valid_to);
4012 free_recipients(valid_cc);
4013 free_recipients(valid_bcc);
4019 /* Is this a room which has anonymous-only or anonymous-option? */
4020 anonymous = MES_NORMAL;
4021 if (CC->room.QRflags & QR_ANONONLY) {
4022 anonymous = MES_ANONONLY;
4024 if (CC->room.QRflags & QR_ANONOPT) {
4025 if (anon_flag == 1) { /* only if the user requested it */
4026 anonymous = MES_ANONOPT;
4030 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4034 /* Recommend to the client that the use of a message subject is
4035 * strongly recommended in this room, if either the SUBJECTREQ flag
4036 * is set, or if there is one or more Internet email recipients.
4038 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4039 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4040 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4041 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4043 /* If we're only checking the validity of the request, return
4044 * success without creating the message.
4047 cprintf("%d %s|%d\n", CIT_OK,
4048 ((valid_to != NULL) ? valid_to->display_recp : ""),
4050 free_recipients(valid_to);
4051 free_recipients(valid_cc);
4052 free_recipients(valid_bcc);
4056 /* We don't need these anymore because we'll do it differently below */
4057 free_recipients(valid_to);
4058 free_recipients(valid_cc);
4059 free_recipients(valid_bcc);
4061 /* Read in the message from the client. */
4063 cprintf("%d send message\n", START_CHAT_MODE);
4065 cprintf("%d send message\n", SEND_LISTING);
4068 msg = CtdlMakeMessage(&CC->user, recp, cc,
4069 CC->room.QRname, anonymous, format_type,
4070 newusername, newuseremail, subject,
4071 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4074 /* Put together one big recipients struct containing to/cc/bcc all in
4075 * one. This is for the envelope.
4077 char *all_recps = malloc(SIZ * 3);
4078 strcpy(all_recps, recp);
4079 if (!IsEmptyStr(cc)) {
4080 if (!IsEmptyStr(all_recps)) {
4081 strcat(all_recps, ",");
4083 strcat(all_recps, cc);
4085 if (!IsEmptyStr(bcc)) {
4086 if (!IsEmptyStr(all_recps)) {
4087 strcat(all_recps, ",");
4089 strcat(all_recps, bcc);
4091 if (!IsEmptyStr(all_recps)) {
4092 valid = validate_recipients(all_recps, NULL, 0);
4100 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4103 cprintf("%ld\n", msgnum);
4105 cprintf("Message accepted.\n");
4108 cprintf("Internal error.\n");
4110 if (msg->cm_fields['E'] != NULL) {
4111 cprintf("%s\n", msg->cm_fields['E']);
4118 CtdlFreeMessage(msg);
4120 if (valid != NULL) {
4121 free_recipients(valid);
4129 * API function to delete messages which match a set of criteria
4130 * (returns the actual number of messages deleted)
4132 int CtdlDeleteMessages(char *room_name, /* which room */
4133 long *dmsgnums, /* array of msg numbers to be deleted */
4134 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4135 char *content_type /* or "" for any. regular expressions expected. */
4138 struct ctdlroom qrbuf;
4139 struct cdbdata *cdbfr;
4140 long *msglist = NULL;
4141 long *dellist = NULL;
4144 int num_deleted = 0;
4146 struct MetaData smi;
4149 int need_to_free_re = 0;
4151 if (content_type) if (!IsEmptyStr(content_type)) {
4152 regcomp(&re, content_type, 0);
4153 need_to_free_re = 1;
4155 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4156 room_name, num_dmsgnums, content_type);
4158 /* get room record, obtaining a lock... */
4159 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4160 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4162 if (need_to_free_re) regfree(&re);
4163 return (0); /* room not found */
4165 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4167 if (cdbfr != NULL) {
4168 dellist = malloc(cdbfr->len);
4169 msglist = (long *) cdbfr->ptr;
4170 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4171 num_msgs = cdbfr->len / sizeof(long);
4175 for (i = 0; i < num_msgs; ++i) {
4178 /* Set/clear a bit for each criterion */
4180 /* 0 messages in the list or a null list means that we are
4181 * interested in deleting any messages which meet the other criteria.
4183 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4184 delete_this |= 0x01;
4187 for (j=0; j<num_dmsgnums; ++j) {
4188 if (msglist[i] == dmsgnums[j]) {
4189 delete_this |= 0x01;
4194 if (IsEmptyStr(content_type)) {
4195 delete_this |= 0x02;
4197 GetMetaData(&smi, msglist[i]);
4198 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4199 delete_this |= 0x02;
4203 /* Delete message only if all bits are set */
4204 if (delete_this == 0x03) {
4205 dellist[num_deleted++] = msglist[i];
4210 num_msgs = sort_msglist(msglist, num_msgs);
4211 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4212 msglist, (int)(num_msgs * sizeof(long)));
4214 qrbuf.QRhighest = msglist[num_msgs - 1];
4216 CtdlPutRoomLock(&qrbuf);
4218 /* Go through the messages we pulled out of the index, and decrement
4219 * their reference counts by 1. If this is the only room the message
4220 * was in, the reference count will reach zero and the message will
4221 * automatically be deleted from the database. We do this in a
4222 * separate pass because there might be plug-in hooks getting called,
4223 * and we don't want that happening during an S_ROOMS critical
4226 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4227 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4228 AdjRefCount(dellist[i], -1);
4231 /* Now free the memory we used, and go away. */
4232 if (msglist != NULL) free(msglist);
4233 if (dellist != NULL) free(dellist);
4234 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4235 if (need_to_free_re) regfree(&re);
4236 return (num_deleted);
4242 * Check whether the current user has permission to delete messages from
4243 * the current room (returns 1 for yes, 0 for no)
4245 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4247 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4248 if (ra & UA_DELETEALLOWED) return(1);
4256 * Delete message from current room
4258 void cmd_dele(char *args)
4267 extract_token(msgset, args, 0, '|', sizeof msgset);
4268 num_msgs = num_tokens(msgset, ',');
4270 cprintf("%d Nothing to do.\n", CIT_OK);
4274 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4275 cprintf("%d Higher access required.\n",
4276 ERROR + HIGHER_ACCESS_REQUIRED);
4281 * Build our message set to be moved/copied
4283 msgs = malloc(num_msgs * sizeof(long));
4284 for (i=0; i<num_msgs; ++i) {
4285 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4286 msgs[i] = atol(msgtok);
4289 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4293 cprintf("%d %d message%s deleted.\n", CIT_OK,
4294 num_deleted, ((num_deleted != 1) ? "s" : ""));
4296 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4304 * move or copy a message to another room
4306 void cmd_move(char *args)
4313 char targ[ROOMNAMELEN];
4314 struct ctdlroom qtemp;
4321 extract_token(msgset, args, 0, '|', sizeof msgset);
4322 num_msgs = num_tokens(msgset, ',');
4324 cprintf("%d Nothing to do.\n", CIT_OK);
4328 extract_token(targ, args, 1, '|', sizeof targ);
4329 convert_room_name_macros(targ, sizeof targ);
4330 targ[ROOMNAMELEN - 1] = 0;
4331 is_copy = extract_int(args, 2);
4333 if (CtdlGetRoom(&qtemp, targ) != 0) {
4334 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4338 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4339 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4343 CtdlGetUser(&CC->user, CC->curr_user);
4344 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4346 /* Check for permission to perform this operation.
4347 * Remember: "CC->room" is source, "qtemp" is target.
4351 /* Aides can move/copy */
4352 if (CC->user.axlevel >= 6) permit = 1;
4354 /* Room aides can move/copy */
4355 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4357 /* Permit move/copy from personal rooms */
4358 if ((CC->room.QRflags & QR_MAILBOX)
4359 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4361 /* Permit only copy from public to personal room */
4363 && (!(CC->room.QRflags & QR_MAILBOX))
4364 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4366 /* Permit message removal from collaborative delete rooms */
4367 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4369 /* Users allowed to post into the target room may move into it too. */
4370 if ((CC->room.QRflags & QR_MAILBOX) &&
4371 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4373 /* User must have access to target room */
4374 if (!(ra & UA_KNOWN)) permit = 0;
4377 cprintf("%d Higher access required.\n",
4378 ERROR + HIGHER_ACCESS_REQUIRED);
4383 * Build our message set to be moved/copied
4385 msgs = malloc(num_msgs * sizeof(long));
4386 for (i=0; i<num_msgs; ++i) {
4387 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4388 msgs[i] = atol(msgtok);
4394 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4396 cprintf("%d Cannot store message(s) in %s: error %d\n",
4402 /* Now delete the message from the source room,
4403 * if this is a 'move' rather than a 'copy' operation.
4406 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4410 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4416 * GetMetaData() - Get the supplementary record for a message
4418 void GetMetaData(struct MetaData *smibuf, long msgnum)
4421 struct cdbdata *cdbsmi;
4424 memset(smibuf, 0, sizeof(struct MetaData));
4425 smibuf->meta_msgnum = msgnum;
4426 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4428 /* Use the negative of the message number for its supp record index */
4429 TheIndex = (0L - msgnum);
4431 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4432 if (cdbsmi == NULL) {
4433 return; /* record not found; go with defaults */
4435 memcpy(smibuf, cdbsmi->ptr,
4436 ((cdbsmi->len > sizeof(struct MetaData)) ?
4437 sizeof(struct MetaData) : cdbsmi->len));
4444 * PutMetaData() - (re)write supplementary record for a message
4446 void PutMetaData(struct MetaData *smibuf)
4450 /* Use the negative of the message number for the metadata db index */
4451 TheIndex = (0L - smibuf->meta_msgnum);
4453 cdb_store(CDB_MSGMAIN,
4454 &TheIndex, (int)sizeof(long),
4455 smibuf, (int)sizeof(struct MetaData));
4460 * AdjRefCount - submit an adjustment to the reference count for a message.
4461 * (These are just queued -- we actually process them later.)
4463 void AdjRefCount(long msgnum, int incr)
4465 struct arcq new_arcq;
4468 begin_critical_section(S_SUPPMSGMAIN);
4469 if (arcfp == NULL) {
4470 arcfp = fopen(file_arcq, "ab+");
4472 end_critical_section(S_SUPPMSGMAIN);
4474 /* msgnum < 0 means that we're trying to close the file */
4476 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4477 begin_critical_section(S_SUPPMSGMAIN);
4478 if (arcfp != NULL) {
4482 end_critical_section(S_SUPPMSGMAIN);
4487 * If we can't open the queue, perform the operation synchronously.
4489 if (arcfp == NULL) {
4490 TDAP_AdjRefCount(msgnum, incr);
4494 new_arcq.arcq_msgnum = msgnum;
4495 new_arcq.arcq_delta = incr;
4496 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4504 * TDAP_ProcessAdjRefCountQueue()
4506 * Process the queue of message count adjustments that was created by calls
4507 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4508 * for each one. This should be an "off hours" operation.
4510 int TDAP_ProcessAdjRefCountQueue(void)
4512 char file_arcq_temp[PATH_MAX];
4515 struct arcq arcq_rec;
4516 int num_records_processed = 0;
4518 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4520 begin_critical_section(S_SUPPMSGMAIN);
4521 if (arcfp != NULL) {
4526 r = link(file_arcq, file_arcq_temp);
4528 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4529 end_critical_section(S_SUPPMSGMAIN);
4530 return(num_records_processed);
4534 end_critical_section(S_SUPPMSGMAIN);
4536 fp = fopen(file_arcq_temp, "rb");
4538 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4539 return(num_records_processed);
4542 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4543 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4544 ++num_records_processed;
4548 r = unlink(file_arcq_temp);
4550 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4553 return(num_records_processed);
4559 * TDAP_AdjRefCount - adjust the reference count for a message.
4560 * This one does it "for real" because it's called by
4561 * the autopurger function that processes the queue
4562 * created by AdjRefCount(). If a message's reference
4563 * count becomes zero, we also delete the message from
4564 * disk and de-index it.
4566 void TDAP_AdjRefCount(long msgnum, int incr)
4569 struct MetaData smi;
4572 /* This is a *tight* critical section; please keep it that way, as
4573 * it may get called while nested in other critical sections.
4574 * Complicating this any further will surely cause deadlock!
4576 begin_critical_section(S_SUPPMSGMAIN);
4577 GetMetaData(&smi, msgnum);
4578 smi.meta_refcount += incr;
4580 end_critical_section(S_SUPPMSGMAIN);
4581 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4582 msgnum, incr, smi.meta_refcount);
4584 /* If the reference count is now zero, delete the message
4585 * (and its supplementary record as well).
4587 if (smi.meta_refcount == 0) {
4588 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4590 /* Call delete hooks with NULL room to show it has gone altogether */
4591 PerformDeleteHooks(NULL, msgnum);
4593 /* Remove from message base */
4595 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4596 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4598 /* Remove metadata record */
4599 delnum = (0L - msgnum);
4600 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4606 * Write a generic object to this room
4608 * Note: this could be much more efficient. Right now we use two temporary
4609 * files, and still pull the message into memory as with all others.
4611 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4612 char *content_type, /* MIME type of this object */
4613 char *raw_message, /* Data to be written */
4614 off_t raw_length, /* Size of raw_message */
4615 struct ctdluser *is_mailbox, /* Mailbox room? */
4616 int is_binary, /* Is encoding necessary? */
4617 int is_unique, /* Del others of this type? */
4618 unsigned int flags /* Internal save flags */
4622 struct ctdlroom qrbuf;
4623 char roomname[ROOMNAMELEN];
4624 struct CtdlMessage *msg;
4625 char *encoded_message = NULL;
4627 if (is_mailbox != NULL) {
4628 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4631 safestrncpy(roomname, req_room, sizeof(roomname));
4634 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4637 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4640 encoded_message = malloc((size_t)(raw_length + 4096));
4643 sprintf(encoded_message, "Content-type: %s\n", content_type);
4646 sprintf(&encoded_message[strlen(encoded_message)],
4647 "Content-transfer-encoding: base64\n\n"
4651 sprintf(&encoded_message[strlen(encoded_message)],
4652 "Content-transfer-encoding: 7bit\n\n"
4658 &encoded_message[strlen(encoded_message)],
4666 &encoded_message[strlen(encoded_message)],
4672 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4673 msg = malloc(sizeof(struct CtdlMessage));
4674 memset(msg, 0, sizeof(struct CtdlMessage));
4675 msg->cm_magic = CTDLMESSAGE_MAGIC;
4676 msg->cm_anon_type = MES_NORMAL;
4677 msg->cm_format_type = 4;
4678 msg->cm_fields['A'] = strdup(CC->user.fullname);
4679 msg->cm_fields['O'] = strdup(req_room);
4680 msg->cm_fields['N'] = strdup(config.c_nodename);
4681 msg->cm_fields['H'] = strdup(config.c_humannode);
4682 msg->cm_flags = flags;
4684 msg->cm_fields['M'] = encoded_message;
4686 /* Create the requested room if we have to. */
4687 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4688 CtdlCreateRoom(roomname,
4689 ( (is_mailbox != NULL) ? 5 : 3 ),
4690 "", 0, 1, 0, VIEW_BBS);
4692 /* If the caller specified this object as unique, delete all
4693 * other objects of this type that are currently in the room.
4696 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4697 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4700 /* Now write the data */
4701 CtdlSubmitMsg(msg, NULL, roomname, 0);
4702 CtdlFreeMessage(msg);
4710 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4711 config_msgnum = msgnum;
4715 char *CtdlGetSysConfig(char *sysconfname) {
4716 char hold_rm[ROOMNAMELEN];
4719 struct CtdlMessage *msg;
4722 strcpy(hold_rm, CC->room.QRname);
4723 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4724 CtdlGetRoom(&CC->room, hold_rm);
4729 /* We want the last (and probably only) config in this room */
4730 begin_critical_section(S_CONFIG);
4731 config_msgnum = (-1L);
4732 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4733 CtdlGetSysConfigBackend, NULL);
4734 msgnum = config_msgnum;
4735 end_critical_section(S_CONFIG);
4741 msg = CtdlFetchMessage(msgnum, 1);
4743 conf = strdup(msg->cm_fields['M']);
4744 CtdlFreeMessage(msg);
4751 CtdlGetRoom(&CC->room, hold_rm);
4753 if (conf != NULL) do {
4754 extract_token(buf, conf, 0, '\n', sizeof buf);
4755 strcpy(conf, &conf[strlen(buf)+1]);
4756 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4762 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4763 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4768 * Determine whether a given Internet address belongs to the current user
4770 int CtdlIsMe(char *addr, int addr_buf_len)
4772 struct recptypes *recp;
4775 recp = validate_recipients(addr, NULL, 0);
4776 if (recp == NULL) return(0);
4778 if (recp->num_local == 0) {
4779 free_recipients(recp);
4783 for (i=0; i<recp->num_local; ++i) {
4784 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4785 if (!strcasecmp(addr, CC->user.fullname)) {
4786 free_recipients(recp);
4791 free_recipients(recp);
4797 * Citadel protocol command to do the same
4799 void cmd_isme(char *argbuf) {
4802 if (CtdlAccessCheck(ac_logged_in)) return;
4803 extract_token(addr, argbuf, 0, '|', sizeof addr);
4805 if (CtdlIsMe(addr, sizeof addr)) {
4806 cprintf("%d %s\n", CIT_OK, addr);
4809 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4815 /*****************************************************************************/
4816 /* MODULE INITIALIZATION STUFF */
4817 /*****************************************************************************/
4819 CTDL_MODULE_INIT(msgbase)
4822 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4823 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4824 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4825 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4826 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4827 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4828 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4829 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4830 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4831 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4832 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4833 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4836 /* return our Subversion id for the Log */