4 * Implements the message store.
6 * Copyright (c) 1987-2009 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);
301 /* Determine if a given message matches the fields in a message template.
302 * Return 0 for a successful match.
304 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
307 /* If there aren't any fields in the template, all messages will
310 if (template == NULL) return(0);
312 /* Null messages are bogus. */
313 if (msg == NULL) return(1);
315 for (i='A'; i<='Z'; ++i) {
316 if (template->cm_fields[i] != NULL) {
317 if (msg->cm_fields[i] == NULL) {
318 /* Considered equal if temmplate is empty string */
319 if (IsEmptyStr(template->cm_fields[i])) continue;
322 if (strcasecmp(msg->cm_fields[i],
323 template->cm_fields[i])) return 1;
327 /* All compares succeeded: we have a match! */
334 * Retrieve the "seen" message list for the current room.
336 void CtdlGetSeen(char *buf, int which_set) {
339 /* Learn about the user and room in question */
340 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
342 if (which_set == ctdlsetseen_seen)
343 safestrncpy(buf, vbuf.v_seen, SIZ);
344 if (which_set == ctdlsetseen_answered)
345 safestrncpy(buf, vbuf.v_answered, SIZ);
351 * Manipulate the "seen msgs" string (or other message set strings)
353 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
354 int target_setting, int which_set,
355 struct ctdluser *which_user, struct ctdlroom *which_room) {
356 struct cdbdata *cdbfr;
370 char *is_set; /* actually an array of booleans */
372 /* Don't bother doing *anything* if we were passed a list of zero messages */
373 if (num_target_msgnums < 1) {
377 /* If no room was specified, we go with the current room. */
379 which_room = &CC->room;
382 /* If no user was specified, we go with the current user. */
384 which_user = &CC->user;
387 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
388 num_target_msgnums, target_msgnums[0],
389 (target_setting ? "SET" : "CLEAR"),
393 /* Learn about the user and room in question */
394 CtdlGetRelationship(&vbuf, which_user, which_room);
396 /* Load the message list */
397 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
399 msglist = (long *) cdbfr->ptr;
400 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
401 num_msgs = cdbfr->len / sizeof(long);
404 return; /* No messages at all? No further action. */
407 is_set = malloc(num_msgs * sizeof(char));
408 memset(is_set, 0, (num_msgs * sizeof(char)) );
410 /* Decide which message set we're manipulating */
412 case ctdlsetseen_seen:
413 vset = NewStrBufPlain(vbuf.v_seen, -1);
415 case ctdlsetseen_answered:
416 vset = NewStrBufPlain(vbuf.v_answered, -1);
423 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
424 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
425 for (i=0; i<num_msgs; ++i) {
426 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
428 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
429 for (k=0; k<num_target_msgnums; ++k) {
430 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
434 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
436 /* Translate the existing sequence set into an array of booleans */
437 setstr = NewStrBuf();
441 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
443 StrBufExtract_token(lostr, setstr, 0, ':');
444 if (StrBufNum_tokens(setstr, ':') >= 2) {
445 StrBufExtract_token(histr, setstr, 1, ':');
449 StrBufAppendBuf(histr, lostr, 0);
452 if (!strcmp(ChrPtr(histr), "*")) {
459 for (i = 0; i < num_msgs; ++i) {
460 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
470 /* Now translate the array of booleans back into a sequence set */
476 for (i=0; i<num_msgs; ++i) {
480 for (k=0; k<num_target_msgnums; ++k) {
481 if (msglist[i] == target_msgnums[k]) {
482 is_seen = target_setting;
486 if ((was_seen == 0) && (is_seen == 1)) {
489 else if ((was_seen == 1) && (is_seen == 0)) {
492 if (StrLength(vset) > 0) {
493 StrBufAppendBufPlain(vset, HKEY(","), 0);
496 StrBufAppendPrintf(vset, "%ld", hi);
499 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
503 if ((is_seen) && (i == num_msgs - 1)) {
504 if (StrLength(vset) > 0) {
505 StrBufAppendBufPlain(vset, HKEY(","), 0);
507 if ((i==0) || (was_seen == 0)) {
508 StrBufAppendPrintf(vset, "%ld", msglist[i]);
511 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
519 * We will have to stuff this string back into a 4096 byte buffer, so if it's
520 * larger than that now, truncate it by removing tokens from the beginning.
521 * The limit of 100 iterations is there to prevent an infinite loop in case
522 * something unexpected happens.
524 int number_of_truncations = 0;
525 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
526 StrBufRemove_token(vset, 0, ',');
527 ++number_of_truncations;
531 * If we're truncating the sequence set of messages marked with the 'seen' flag,
532 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
533 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
535 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
537 first_tok = NewStrBuf();
538 StrBufExtract_token(first_tok, vset, 0, ',');
539 StrBufRemove_token(vset, 0, ',');
541 if (StrBufNum_tokens(first_tok, ':') > 1) {
542 StrBufRemove_token(first_tok, 0, ':');
546 new_set = NewStrBuf();
547 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
548 StrBufAppendBuf(new_set, first_tok, 0);
549 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
550 StrBufAppendBuf(new_set, vset, 0);
553 FreeStrBuf(&first_tok);
557 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
559 /* Decide which message set we're manipulating */
561 case ctdlsetseen_seen:
562 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
564 case ctdlsetseen_answered:
565 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
571 CtdlSetRelationship(&vbuf, which_user, which_room);
577 * API function to perform an operation for each qualifying message in the
578 * current room. (Returns the number of messages processed.)
580 int CtdlForEachMessage(int mode, long ref, char *search_string,
582 struct CtdlMessage *compare,
583 void (*CallBack) (long, void *),
589 struct cdbdata *cdbfr;
590 long *msglist = NULL;
592 int num_processed = 0;
595 struct CtdlMessage *msg = NULL;
598 int printed_lastold = 0;
599 int num_search_msgs = 0;
600 long *search_msgs = NULL;
602 int need_to_free_re = 0;
605 if ((content_type) && (!IsEmptyStr(content_type))) {
606 regcomp(&re, content_type, 0);
610 /* Learn about the user and room in question */
611 getuser(&CC->user, CC->curr_user);
612 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
614 /* Load the message list */
615 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
617 msglist = (long *) cdbfr->ptr;
618 num_msgs = cdbfr->len / sizeof(long);
620 if (need_to_free_re) regfree(&re);
621 return 0; /* No messages at all? No further action. */
626 * Now begin the traversal.
628 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
630 /* If the caller is looking for a specific MIME type, filter
631 * out all messages which are not of the type requested.
633 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
635 /* This call to GetMetaData() sits inside this loop
636 * so that we only do the extra database read per msg
637 * if we need to. Doing the extra read all the time
638 * really kills the server. If we ever need to use
639 * metadata for another search criterion, we need to
640 * move the read somewhere else -- but still be smart
641 * enough to only do the read if the caller has
642 * specified something that will need it.
644 GetMetaData(&smi, msglist[a]);
646 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
647 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
653 num_msgs = sort_msglist(msglist, num_msgs);
655 /* If a template was supplied, filter out the messages which
656 * don't match. (This could induce some delays!)
659 if (compare != NULL) {
660 for (a = 0; a < num_msgs; ++a) {
661 msg = CtdlFetchMessage(msglist[a], 1);
663 if (CtdlMsgCmp(msg, compare)) {
666 CtdlFreeMessage(msg);
672 /* If a search string was specified, get a message list from
673 * the full text index and remove messages which aren't on both
677 * Since the lists are sorted and strictly ascending, and the
678 * output list is guaranteed to be shorter than or equal to the
679 * input list, we overwrite the bottom of the input list. This
680 * eliminates the need to memmove big chunks of the list over and
683 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
685 /* Call search module via hook mechanism.
686 * NULL means use any search function available.
687 * otherwise replace with a char * to name of search routine
689 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
691 if (num_search_msgs > 0) {
695 orig_num_msgs = num_msgs;
697 for (i=0; i<orig_num_msgs; ++i) {
698 for (j=0; j<num_search_msgs; ++j) {
699 if (msglist[i] == search_msgs[j]) {
700 msglist[num_msgs++] = msglist[i];
706 num_msgs = 0; /* No messages qualify */
708 if (search_msgs != NULL) free(search_msgs);
710 /* Now that we've purged messages which don't contain the search
711 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
718 * Now iterate through the message list, according to the
719 * criteria supplied by the caller.
722 for (a = 0; a < num_msgs; ++a) {
723 thismsg = msglist[a];
724 if (mode == MSGS_ALL) {
728 is_seen = is_msg_in_sequence_set(
729 vbuf.v_seen, thismsg);
730 if (is_seen) lastold = thismsg;
736 || ((mode == MSGS_OLD) && (is_seen))
737 || ((mode == MSGS_NEW) && (!is_seen))
738 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
739 || ((mode == MSGS_FIRST) && (a < ref))
740 || ((mode == MSGS_GT) && (thismsg > ref))
741 || ((mode == MSGS_EQ) && (thismsg == ref))
744 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
746 CallBack(lastold, userdata);
750 if (CallBack) CallBack(thismsg, userdata);
754 cdb_free(cdbfr); /* Clean up */
755 if (need_to_free_re) regfree(&re);
756 return num_processed;
762 * cmd_msgs() - get list of message #'s in this room
763 * implements the MSGS server command using CtdlForEachMessage()
765 void cmd_msgs(char *cmdbuf)
774 int with_template = 0;
775 struct CtdlMessage *template = NULL;
776 int with_headers = 0;
777 char search_string[1024];
779 extract_token(which, cmdbuf, 0, '|', sizeof which);
780 cm_ref = extract_int(cmdbuf, 1);
781 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
782 with_template = extract_int(cmdbuf, 2);
783 with_headers = extract_int(cmdbuf, 3);
786 if (!strncasecmp(which, "OLD", 3))
788 else if (!strncasecmp(which, "NEW", 3))
790 else if (!strncasecmp(which, "FIRST", 5))
792 else if (!strncasecmp(which, "LAST", 4))
794 else if (!strncasecmp(which, "GT", 2))
796 else if (!strncasecmp(which, "SEARCH", 6))
801 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
802 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
806 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
807 cprintf("%d Full text index is not enabled on this server.\n",
808 ERROR + CMD_NOT_SUPPORTED);
814 cprintf("%d Send template then receive message list\n",
816 template = (struct CtdlMessage *)
817 malloc(sizeof(struct CtdlMessage));
818 memset(template, 0, sizeof(struct CtdlMessage));
819 template->cm_magic = CTDLMESSAGE_MAGIC;
820 template->cm_anon_type = MES_NORMAL;
822 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
823 extract_token(tfield, buf, 0, '|', sizeof tfield);
824 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
825 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
826 if (!strcasecmp(tfield, msgkeys[i])) {
827 template->cm_fields[i] =
835 cprintf("%d \n", LISTING_FOLLOWS);
838 CtdlForEachMessage(mode,
839 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
840 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
843 (with_headers ? headers_listing : simple_listing),
846 if (template != NULL) CtdlFreeMessage(template);
854 * help_subst() - support routine for help file viewer
856 void help_subst(char *strbuf, char *source, char *dest)
861 while (p = pattern2(strbuf, source), (p >= 0)) {
862 strcpy(workbuf, &strbuf[p + strlen(source)]);
863 strcpy(&strbuf[p], dest);
864 strcat(strbuf, workbuf);
869 void do_help_subst(char *buffer)
873 help_subst(buffer, "^nodename", config.c_nodename);
874 help_subst(buffer, "^humannode", config.c_humannode);
875 help_subst(buffer, "^fqdn", config.c_fqdn);
876 help_subst(buffer, "^username", CC->user.fullname);
877 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
878 help_subst(buffer, "^usernum", buf2);
879 help_subst(buffer, "^sysadm", config.c_sysadm);
880 help_subst(buffer, "^variantname", CITADEL);
881 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
882 help_subst(buffer, "^maxsessions", buf2);
883 help_subst(buffer, "^bbsdir", ctdl_message_dir);
889 * memfmout() - Citadel text formatter and paginator.
890 * Although the original purpose of this routine was to format
891 * text to the reader's screen width, all we're really using it
892 * for here is to format text out to 80 columns before sending it
893 * to the client. The client software may reformat it again.
896 char *mptr, /* where are we going to get our text from? */
897 char subst, /* nonzero if we should do substitutions */
898 char *nl) /* string to terminate lines with */
906 static int width = 80;
911 c = 1; /* c is the current pos */
915 while (ch = *mptr, ((ch != 0) && (strlen(buffer) < 126))) {
917 buffer[strlen(buffer) + 1] = 0;
918 buffer[strlen(buffer)] = ch;
921 if (buffer[0] == '^')
922 do_help_subst(buffer);
924 buffer[strlen(buffer) + 1] = 0;
926 strcpy(buffer, &buffer[1]);
934 if (((ch == 13) || (ch == 10)) && (old != 13) && (old != 10)) {
937 if (((old == 13) || (old == 10)) && (isspace(real))) {
942 if (((strlen(aaa) + c) > (width - 5)) && (strlen(aaa) > (width - 5))) {
943 cprintf("%s%s", nl, aaa);
952 if ((strlen(aaa) + c) > (width - 5)) {
961 if ((ch == 13) || (ch == 10)) {
962 cprintf("%s%s", aaa, nl);
969 cprintf("%s%s", aaa, nl);
975 * Callback function for mime parser that simply lists the part
977 void list_this_part(char *name, char *filename, char *partnum, char *disp,
978 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
979 char *cbid, void *cbuserdata)
983 ma = (struct ma_info *)cbuserdata;
984 if (ma->is_ma == 0) {
985 cprintf("part=%s|%s|%s|%s|%s|%ld|%s\n",
986 name, filename, partnum, disp, cbtype, (long)length, cbid);
991 * Callback function for multipart prefix
993 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
994 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
995 char *cbid, void *cbuserdata)
999 ma = (struct ma_info *)cbuserdata;
1000 if (!strcasecmp(cbtype, "multipart/alternative")) {
1004 if (ma->is_ma == 0) {
1005 cprintf("pref=%s|%s\n", partnum, cbtype);
1010 * Callback function for multipart sufffix
1012 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1013 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1014 char *cbid, void *cbuserdata)
1018 ma = (struct ma_info *)cbuserdata;
1019 if (ma->is_ma == 0) {
1020 cprintf("suff=%s|%s\n", partnum, cbtype);
1022 if (!strcasecmp(cbtype, "multipart/alternative")) {
1029 * Callback function for mime parser that opens a section for downloading
1031 void mime_download(char *name, char *filename, char *partnum, char *disp,
1032 void *content, char *cbtype, char *cbcharset, size_t length,
1033 char *encoding, char *cbid, void *cbuserdata)
1037 /* Silently go away if there's already a download open. */
1038 if (CC->download_fp != NULL)
1042 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1043 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1045 CC->download_fp = tmpfile();
1046 if (CC->download_fp == NULL)
1049 rv = fwrite(content, length, 1, CC->download_fp);
1050 fflush(CC->download_fp);
1051 rewind(CC->download_fp);
1053 OpenCmdResult(filename, cbtype);
1060 * Callback function for mime parser that outputs a section all at once.
1061 * We can specify the desired section by part number *or* content-id.
1063 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1064 void *content, char *cbtype, char *cbcharset, size_t length,
1065 char *encoding, char *cbid, void *cbuserdata)
1067 int *found_it = (int *)cbuserdata;
1070 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1071 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1074 cprintf("%d %d|-1|%s|%s\n",
1080 client_write(content, length);
1087 * Load a message from disk into memory.
1088 * This is used by CtdlOutputMsg() and other fetch functions.
1090 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1091 * using the CtdlMessageFree() function.
1093 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1095 struct cdbdata *dmsgtext;
1096 struct CtdlMessage *ret = NULL;
1100 cit_uint8_t field_header;
1102 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1104 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1105 if (dmsgtext == NULL) {
1108 mptr = dmsgtext->ptr;
1109 upper_bound = mptr + dmsgtext->len;
1111 /* Parse the three bytes that begin EVERY message on disk.
1112 * The first is always 0xFF, the on-disk magic number.
1113 * The second is the anonymous/public type byte.
1114 * The third is the format type byte (vari, fixed, or MIME).
1118 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1122 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1123 memset(ret, 0, sizeof(struct CtdlMessage));
1125 ret->cm_magic = CTDLMESSAGE_MAGIC;
1126 ret->cm_anon_type = *mptr++; /* Anon type byte */
1127 ret->cm_format_type = *mptr++; /* Format type byte */
1130 * The rest is zero or more arbitrary fields. Load them in.
1131 * We're done when we encounter either a zero-length field or
1132 * have just processed the 'M' (message text) field.
1135 if (mptr >= upper_bound) {
1138 field_header = *mptr++;
1139 ret->cm_fields[field_header] = strdup(mptr);
1141 while (*mptr++ != 0); /* advance to next field */
1143 } while ((mptr < upper_bound) && (field_header != 'M'));
1147 /* Always make sure there's something in the msg text field. If
1148 * it's NULL, the message text is most likely stored separately,
1149 * so go ahead and fetch that. Failing that, just set a dummy
1150 * body so other code doesn't barf.
1152 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1153 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1154 if (dmsgtext != NULL) {
1155 ret->cm_fields['M'] = strdup(dmsgtext->ptr);
1159 if (ret->cm_fields['M'] == NULL) {
1160 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1163 /* Perform "before read" hooks (aborting if any return nonzero) */
1164 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1165 CtdlFreeMessage(ret);
1174 * Returns 1 if the supplied pointer points to a valid Citadel message.
1175 * If the pointer is NULL or the magic number check fails, returns 0.
1177 int is_valid_message(struct CtdlMessage *msg) {
1180 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1181 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1189 * 'Destructor' for struct CtdlMessage
1191 void CtdlFreeMessage(struct CtdlMessage *msg)
1195 if (is_valid_message(msg) == 0)
1197 if (msg != NULL) free (msg);
1201 for (i = 0; i < 256; ++i)
1202 if (msg->cm_fields[i] != NULL) {
1203 free(msg->cm_fields[i]);
1206 msg->cm_magic = 0; /* just in case */
1212 * Pre callback function for multipart/alternative
1214 * NOTE: this differs from the standard behavior for a reason. Normally when
1215 * displaying multipart/alternative you want to show the _last_ usable
1216 * format in the message. Here we show the _first_ one, because it's
1217 * usually text/plain. Since this set of functions is designed for text
1218 * output to non-MIME-aware clients, this is the desired behavior.
1221 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1222 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1223 char *cbid, void *cbuserdata)
1227 ma = (struct ma_info *)cbuserdata;
1228 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1229 if (!strcasecmp(cbtype, "multipart/alternative")) {
1233 if (!strcasecmp(cbtype, "message/rfc822")) {
1239 * Post callback function for multipart/alternative
1241 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1242 void *content, char *cbtype, char *cbcharset, size_t length,
1243 char *encoding, char *cbid, void *cbuserdata)
1247 ma = (struct ma_info *)cbuserdata;
1248 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1249 if (!strcasecmp(cbtype, "multipart/alternative")) {
1253 if (!strcasecmp(cbtype, "message/rfc822")) {
1259 * Inline callback function for mime parser that wants to display text
1261 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1262 void *content, char *cbtype, char *cbcharset, size_t length,
1263 char *encoding, char *cbid, void *cbuserdata)
1270 ma = (struct ma_info *)cbuserdata;
1272 CtdlLogPrintf(CTDL_DEBUG,
1273 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1274 partnum, filename, cbtype, (long)length);
1277 * If we're in the middle of a multipart/alternative scope and
1278 * we've already printed another section, skip this one.
1280 if ( (ma->is_ma) && (ma->did_print) ) {
1281 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1286 if ( (!strcasecmp(cbtype, "text/plain"))
1287 || (IsEmptyStr(cbtype)) ) {
1290 client_write(wptr, length);
1291 if (wptr[length-1] != '\n') {
1298 if (!strcasecmp(cbtype, "text/html")) {
1299 ptr = html_to_ascii(content, length, 80, 0);
1301 client_write(ptr, wlen);
1302 if (ptr[wlen-1] != '\n') {
1309 if (ma->use_fo_hooks) {
1310 if (PerformFixedOutputHooks(cbtype, content, length)) {
1311 /* above function returns nonzero if it handled the part */
1316 if (strncasecmp(cbtype, "multipart/", 10)) {
1317 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1318 partnum, filename, cbtype, (long)length);
1324 * The client is elegant and sophisticated and wants to be choosy about
1325 * MIME content types, so figure out which multipart/alternative part
1326 * we're going to send.
1328 * We use a system of weights. When we find a part that matches one of the
1329 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1330 * and then set ma->chosen_pref to that MIME type's position in our preference
1331 * list. If we then hit another match, we only replace the first match if
1332 * the preference value is lower.
1334 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1335 void *content, char *cbtype, char *cbcharset, size_t length,
1336 char *encoding, char *cbid, void *cbuserdata)
1342 ma = (struct ma_info *)cbuserdata;
1344 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1345 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1346 // I don't know if there are any side effects! Please TEST TEST TEST
1347 //if (ma->is_ma > 0) {
1349 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1350 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1351 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1352 if (i < ma->chosen_pref) {
1353 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1354 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1355 ma->chosen_pref = i;
1362 * Now that we've chosen our preferred part, output it.
1364 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1365 void *content, char *cbtype, char *cbcharset, size_t length,
1366 char *encoding, char *cbid, void *cbuserdata)
1370 int add_newline = 0;
1374 ma = (struct ma_info *)cbuserdata;
1376 /* This is not the MIME part you're looking for... */
1377 if (strcasecmp(partnum, ma->chosen_part)) return;
1379 /* If the content-type of this part is in our preferred formats
1380 * list, we can simply output it verbatim.
1382 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1383 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1384 if (!strcasecmp(buf, cbtype)) {
1385 /* Yeah! Go! W00t!! */
1387 text_content = (char *)content;
1388 if (text_content[length-1] != '\n') {
1391 cprintf("Content-type: %s", cbtype);
1392 if (!IsEmptyStr(cbcharset)) {
1393 cprintf("; charset=%s", cbcharset);
1395 cprintf("\nContent-length: %d\n",
1396 (int)(length + add_newline) );
1397 if (!IsEmptyStr(encoding)) {
1398 cprintf("Content-transfer-encoding: %s\n", encoding);
1401 cprintf("Content-transfer-encoding: 7bit\n");
1403 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1405 client_write(content, length);
1406 if (add_newline) cprintf("\n");
1411 /* No translations required or possible: output as text/plain */
1412 cprintf("Content-type: text/plain\n\n");
1413 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1414 length, encoding, cbid, cbuserdata);
1419 char desired_section[64];
1426 * Callback function for
1428 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1429 void *content, char *cbtype, char *cbcharset, size_t length,
1430 char *encoding, char *cbid, void *cbuserdata)
1432 struct encapmsg *encap;
1434 encap = (struct encapmsg *)cbuserdata;
1436 /* Only proceed if this is the desired section... */
1437 if (!strcasecmp(encap->desired_section, partnum)) {
1438 encap->msglen = length;
1439 encap->msg = malloc(length + 2);
1440 memcpy(encap->msg, content, length);
1451 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1452 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1453 return(om_not_logged_in);
1460 * Get a message off disk. (returns om_* values found in msgbase.h)
1463 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1464 int mode, /* how would you like that message? */
1465 int headers_only, /* eschew the message body? */
1466 int do_proto, /* do Citadel protocol responses? */
1467 int crlf, /* Use CRLF newlines instead of LF? */
1468 char *section, /* NULL or a message/rfc822 section */
1469 int flags /* various flags; see msgbase.h */
1471 struct CtdlMessage *TheMessage = NULL;
1472 int retcode = om_no_such_msg;
1473 struct encapmsg encap;
1476 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1478 (section ? section : "<>")
1481 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1484 if (r == om_not_logged_in) {
1485 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1488 cprintf("%d An unknown error has occurred.\n", ERROR);
1494 /* FIXME: check message id against msglist for this room */
1497 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1498 * request that we don't even bother loading the body into memory.
1500 if (headers_only == HEADERS_FAST) {
1501 TheMessage = CtdlFetchMessage(msg_num, 0);
1504 TheMessage = CtdlFetchMessage(msg_num, 1);
1507 if (TheMessage == NULL) {
1508 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1509 ERROR + MESSAGE_NOT_FOUND, msg_num);
1510 return(om_no_such_msg);
1513 /* Here is the weird form of this command, to process only an
1514 * encapsulated message/rfc822 section.
1516 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1517 memset(&encap, 0, sizeof encap);
1518 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1519 mime_parser(TheMessage->cm_fields['M'],
1521 *extract_encapsulated_message,
1522 NULL, NULL, (void *)&encap, 0
1524 CtdlFreeMessage(TheMessage);
1528 encap.msg[encap.msglen] = 0;
1529 TheMessage = convert_internet_message(encap.msg);
1530 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1532 /* Now we let it fall through to the bottom of this
1533 * function, because TheMessage now contains the
1534 * encapsulated message instead of the top-level
1535 * message. Isn't that neat?
1540 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1541 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1542 retcode = om_no_such_msg;
1547 /* Ok, output the message now */
1548 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1549 CtdlFreeMessage(TheMessage);
1555 char *qp_encode_email_addrs(char *source)
1557 char user[256], node[256], name[256];
1558 const char headerStr[] = "=?UTF-8?Q?";
1562 int need_to_encode = 0;
1568 long nAddrPtrMax = 50;
1573 if (source == NULL) return source;
1574 if (IsEmptyStr(source)) return source;
1576 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1577 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1578 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1581 while (!IsEmptyStr (&source[i])) {
1582 if (nColons >= nAddrPtrMax){
1585 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1586 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1587 free (AddrPtr), AddrPtr = ptr;
1589 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1590 memset(&ptr[nAddrPtrMax], 0,
1591 sizeof (long) * nAddrPtrMax);
1593 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1594 free (AddrUtf8), AddrUtf8 = ptr;
1597 if (((unsigned char) source[i] < 32) ||
1598 ((unsigned char) source[i] > 126)) {
1600 AddrUtf8[nColons] = 1;
1602 if (source[i] == '"')
1603 InQuotes = !InQuotes;
1604 if (!InQuotes && source[i] == ',') {
1605 AddrPtr[nColons] = i;
1610 if (need_to_encode == 0) {
1617 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1618 Encoded = (char*) malloc (EncodedMaxLen);
1620 for (i = 0; i < nColons; i++)
1621 source[AddrPtr[i]++] = '\0';
1625 for (i = 0; i < nColons && nPtr != NULL; i++) {
1626 nmax = EncodedMaxLen - (nPtr - Encoded);
1628 process_rfc822_addr(&source[AddrPtr[i]],
1632 /* TODO: libIDN here ! */
1633 if (IsEmptyStr(name)) {
1634 n = snprintf(nPtr, nmax,
1635 (i==0)?"%s@%s" : ",%s@%s",
1639 EncodedName = rfc2047encode(name, strlen(name));
1640 n = snprintf(nPtr, nmax,
1641 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1642 EncodedName, user, node);
1647 n = snprintf(nPtr, nmax,
1648 (i==0)?"%s" : ",%s",
1649 &source[AddrPtr[i]]);
1655 ptr = (char*) malloc(EncodedMaxLen * 2);
1656 memcpy(ptr, Encoded, EncodedMaxLen);
1657 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1658 free(Encoded), Encoded = ptr;
1660 i--; /* do it once more with properly lengthened buffer */
1663 for (i = 0; i < nColons; i++)
1664 source[--AddrPtr[i]] = ',';
1671 /* If the last item in a list of recipients was truncated to a partial address,
1672 * remove it completely in order to avoid choking libSieve
1674 void sanitize_truncated_recipient(char *str)
1677 if (num_tokens(str, ',') < 2) return;
1679 int len = strlen(str);
1680 if (len < 900) return;
1681 if (len > 998) str[998] = 0;
1683 char *cptr = strrchr(str, ',');
1686 char *lptr = strchr(cptr, '<');
1687 char *rptr = strchr(cptr, '>');
1689 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1697 * Get a message off disk. (returns om_* values found in msgbase.h)
1699 int CtdlOutputPreLoadedMsg(
1700 struct CtdlMessage *TheMessage,
1701 int mode, /* how would you like that message? */
1702 int headers_only, /* eschew the message body? */
1703 int do_proto, /* do Citadel protocol responses? */
1704 int crlf, /* Use CRLF newlines instead of LF? */
1705 int flags /* should the bessage be exported clean? */
1709 cit_uint8_t ch, prev_ch;
1711 char display_name[256];
1713 char *nl; /* newline string */
1715 int subject_found = 0;
1718 /* Buffers needed for RFC822 translation. These are all filled
1719 * using functions that are bounds-checked, and therefore we can
1720 * make them substantially smaller than SIZ.
1727 char datestamp[100];
1729 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
1730 ((TheMessage == NULL) ? "NULL" : "not null"),
1731 mode, headers_only, do_proto, crlf);
1733 strcpy(mid, "unknown");
1734 nl = (crlf ? "\r\n" : "\n");
1736 if (!is_valid_message(TheMessage)) {
1737 CtdlLogPrintf(CTDL_ERR,
1738 "ERROR: invalid preloaded message for output\n");
1740 return(om_no_such_msg);
1743 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
1744 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
1746 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
1747 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
1750 /* Are we downloading a MIME component? */
1751 if (mode == MT_DOWNLOAD) {
1752 if (TheMessage->cm_format_type != FMT_RFC822) {
1754 cprintf("%d This is not a MIME message.\n",
1755 ERROR + ILLEGAL_VALUE);
1756 } else if (CC->download_fp != NULL) {
1757 if (do_proto) cprintf(
1758 "%d You already have a download open.\n",
1759 ERROR + RESOURCE_BUSY);
1761 /* Parse the message text component */
1762 mptr = TheMessage->cm_fields['M'];
1763 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
1764 /* If there's no file open by this time, the requested
1765 * section wasn't found, so print an error
1767 if (CC->download_fp == NULL) {
1768 if (do_proto) cprintf(
1769 "%d Section %s not found.\n",
1770 ERROR + FILE_NOT_FOUND,
1771 CC->download_desired_section);
1774 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1777 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
1778 * in a single server operation instead of opening a download file.
1780 if (mode == MT_SPEW_SECTION) {
1781 if (TheMessage->cm_format_type != FMT_RFC822) {
1783 cprintf("%d This is not a MIME message.\n",
1784 ERROR + ILLEGAL_VALUE);
1786 /* Parse the message text component */
1789 mptr = TheMessage->cm_fields['M'];
1790 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
1791 /* If section wasn't found, print an error
1794 if (do_proto) cprintf(
1795 "%d Section %s not found.\n",
1796 ERROR + FILE_NOT_FOUND,
1797 CC->download_desired_section);
1800 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
1803 /* now for the user-mode message reading loops */
1804 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
1806 /* Does the caller want to skip the headers? */
1807 if (headers_only == HEADERS_NONE) goto START_TEXT;
1809 /* Tell the client which format type we're using. */
1810 if ( (mode == MT_CITADEL) && (do_proto) ) {
1811 cprintf("type=%d\n", TheMessage->cm_format_type);
1814 /* nhdr=yes means that we're only displaying headers, no body */
1815 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
1816 && ((mode == MT_CITADEL) || (mode == MT_MIME))
1819 cprintf("nhdr=yes\n");
1822 /* begin header processing loop for Citadel message format */
1824 if ((mode == MT_CITADEL) || (mode == MT_MIME)) {
1826 safestrncpy(display_name, "<unknown>", sizeof display_name);
1827 if (TheMessage->cm_fields['A']) {
1828 strcpy(buf, TheMessage->cm_fields['A']);
1829 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1830 safestrncpy(display_name, "****", sizeof display_name);
1832 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1833 safestrncpy(display_name, "anonymous", sizeof display_name);
1836 safestrncpy(display_name, buf, sizeof display_name);
1838 if ((is_room_aide())
1839 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1840 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1841 size_t tmp = strlen(display_name);
1842 snprintf(&display_name[tmp],
1843 sizeof display_name - tmp,
1848 /* Don't show Internet address for users on the
1849 * local Citadel network.
1852 if (TheMessage->cm_fields['N'] != NULL)
1853 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1854 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1858 /* Now spew the header fields in the order we like them. */
1859 safestrncpy(allkeys, FORDER, sizeof allkeys);
1860 for (i=0; i<strlen(allkeys); ++i) {
1861 k = (int) allkeys[i];
1863 if ( (TheMessage->cm_fields[k] != NULL)
1864 && (msgkeys[k] != NULL) ) {
1865 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1866 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1869 if (do_proto) cprintf("%s=%s\n",
1873 else if ((k == 'F') && (suppress_f)) {
1876 /* Masquerade display name if needed */
1878 if (do_proto) cprintf("%s=%s\n",
1880 TheMessage->cm_fields[k]
1889 /* begin header processing loop for RFC822 transfer format */
1894 strcpy(snode, NODENAME);
1895 if (mode == MT_RFC822) {
1896 for (i = 0; i < 256; ++i) {
1897 if (TheMessage->cm_fields[i]) {
1898 mptr = mpptr = TheMessage->cm_fields[i];
1901 safestrncpy(luser, mptr, sizeof luser);
1902 safestrncpy(suser, mptr, sizeof suser);
1904 else if (i == 'Y') {
1905 if ((flags & QP_EADDR) != 0) {
1906 mptr = qp_encode_email_addrs(mptr);
1908 sanitize_truncated_recipient(mptr);
1909 cprintf("CC: %s%s", mptr, nl);
1911 else if (i == 'P') {
1912 cprintf("Return-Path: %s%s", mptr, nl);
1914 else if (i == 'L') {
1915 cprintf("List-ID: %s%s", mptr, nl);
1917 else if (i == 'V') {
1918 if ((flags & QP_EADDR) != 0)
1919 mptr = qp_encode_email_addrs(mptr);
1920 cprintf("Envelope-To: %s%s", mptr, nl);
1922 else if (i == 'U') {
1923 cprintf("Subject: %s%s", mptr, nl);
1927 safestrncpy(mid, mptr, sizeof mid);
1929 safestrncpy(fuser, mptr, sizeof fuser);
1930 /* else if (i == 'O')
1931 cprintf("X-Citadel-Room: %s%s",
1934 safestrncpy(snode, mptr, sizeof snode);
1937 if (haschar(mptr, '@') == 0)
1939 sanitize_truncated_recipient(mptr);
1940 cprintf("To: %s@%s", mptr, config.c_fqdn);
1945 if ((flags & QP_EADDR) != 0) {
1946 mptr = qp_encode_email_addrs(mptr);
1948 sanitize_truncated_recipient(mptr);
1949 cprintf("To: %s", mptr);
1953 else if (i == 'T') {
1954 datestring(datestamp, sizeof datestamp,
1955 atol(mptr), DATESTRING_RFC822);
1956 cprintf("Date: %s%s", datestamp, nl);
1958 else if (i == 'W') {
1959 cprintf("References: ");
1960 k = num_tokens(mptr, '|');
1961 for (j=0; j<k; ++j) {
1962 extract_token(buf, mptr, j, '|', sizeof buf);
1963 cprintf("<%s>", buf);
1976 if (subject_found == 0) {
1977 cprintf("Subject: (no subject)%s", nl);
1981 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
1982 suser[i] = tolower(suser[i]);
1983 if (!isalnum(suser[i])) suser[i]='_';
1986 if (mode == MT_RFC822) {
1987 if (!strcasecmp(snode, NODENAME)) {
1988 safestrncpy(snode, FQDN, sizeof snode);
1991 /* Construct a fun message id */
1992 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
1993 if (strchr(mid, '@')==NULL) {
1994 cprintf("@%s", snode);
1998 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
1999 cprintf("From: \"----\" <x@x.org>%s", nl);
2001 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2002 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2004 else if (!IsEmptyStr(fuser)) {
2005 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2008 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2011 /* Blank line signifying RFC822 end-of-headers */
2012 if (TheMessage->cm_format_type != FMT_RFC822) {
2017 /* end header processing loop ... at this point, we're in the text */
2019 if (headers_only == HEADERS_FAST) goto DONE;
2020 mptr = TheMessage->cm_fields['M'];
2022 /* Tell the client about the MIME parts in this message */
2023 if (TheMessage->cm_format_type == FMT_RFC822) {
2024 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2025 memset(&ma, 0, sizeof(struct ma_info));
2026 mime_parser(mptr, NULL,
2027 (do_proto ? *list_this_part : NULL),
2028 (do_proto ? *list_this_pref : NULL),
2029 (do_proto ? *list_this_suff : NULL),
2032 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2033 char *start_of_text = NULL;
2034 start_of_text = strstr(mptr, "\n\r\n");
2035 if (start_of_text == NULL) start_of_text = strstr(mptr, "\n\n");
2036 if (start_of_text == NULL) start_of_text = mptr;
2038 start_of_text = strstr(start_of_text, "\n");
2043 int nllen = strlen(nl);
2045 while (ch=*mptr, ch!=0) {
2051 ((headers_only == HEADERS_NONE) && (mptr >= start_of_text))
2052 || ((headers_only == HEADERS_ONLY) && (mptr < start_of_text))
2053 || ((headers_only != HEADERS_NONE) && (headers_only != HEADERS_ONLY))
2056 sprintf(&outbuf[outlen], "%s", nl);
2060 outbuf[outlen++] = ch;
2064 if (flags & ESC_DOT)
2066 if ((prev_ch == 10) && (ch == '.') && ((*(mptr+1) == 13) || (*(mptr+1) == 10)))
2068 outbuf[outlen++] = '.';
2073 if (outlen > 1000) {
2074 client_write(outbuf, outlen);
2079 client_write(outbuf, outlen);
2087 if (headers_only == HEADERS_ONLY) {
2091 /* signify start of msg text */
2092 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2093 if (do_proto) cprintf("text\n");
2096 /* If the format type on disk is 1 (fixed-format), then we want
2097 * everything to be output completely literally ... regardless of
2098 * what message transfer format is in use.
2100 if (TheMessage->cm_format_type == FMT_FIXED) {
2102 if (mode == MT_MIME) {
2103 cprintf("Content-type: text/plain\n\n");
2107 while (ch = *mptr++, ch > 0) {
2110 if ((ch == 10) || (buflen > 250)) {
2112 cprintf("%s%s", buf, nl);
2121 if (!IsEmptyStr(buf))
2122 cprintf("%s%s", buf, nl);
2125 /* If the message on disk is format 0 (Citadel vari-format), we
2126 * output using the formatter at 80 columns. This is the final output
2127 * form if the transfer format is RFC822, but if the transfer format
2128 * is Citadel proprietary, it'll still work, because the indentation
2129 * for new paragraphs is correct and the client will reformat the
2130 * message to the reader's screen width.
2132 if (TheMessage->cm_format_type == FMT_CITADEL) {
2133 if (mode == MT_MIME) {
2134 cprintf("Content-type: text/x-citadel-variformat\n\n");
2136 memfmout(mptr, 0, nl);
2139 /* If the message on disk is format 4 (MIME), we've gotta hand it
2140 * off to the MIME parser. The client has already been told that
2141 * this message is format 1 (fixed format), so the callback function
2142 * we use will display those parts as-is.
2144 if (TheMessage->cm_format_type == FMT_RFC822) {
2145 memset(&ma, 0, sizeof(struct ma_info));
2147 if (mode == MT_MIME) {
2148 ma.use_fo_hooks = 0;
2149 strcpy(ma.chosen_part, "1");
2150 ma.chosen_pref = 9999;
2151 mime_parser(mptr, NULL,
2152 *choose_preferred, *fixed_output_pre,
2153 *fixed_output_post, (void *)&ma, 0);
2154 mime_parser(mptr, NULL,
2155 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2158 ma.use_fo_hooks = 1;
2159 mime_parser(mptr, NULL,
2160 *fixed_output, *fixed_output_pre,
2161 *fixed_output_post, (void *)&ma, 0);
2166 DONE: /* now we're done */
2167 if (do_proto) cprintf("000\n");
2174 * display a message (mode 0 - Citadel proprietary)
2176 void cmd_msg0(char *cmdbuf)
2179 int headers_only = HEADERS_ALL;
2181 msgid = extract_long(cmdbuf, 0);
2182 headers_only = extract_int(cmdbuf, 1);
2184 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2190 * display a message (mode 2 - RFC822)
2192 void cmd_msg2(char *cmdbuf)
2195 int headers_only = HEADERS_ALL;
2197 msgid = extract_long(cmdbuf, 0);
2198 headers_only = extract_int(cmdbuf, 1);
2200 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2206 * display a message (mode 3 - IGnet raw format - internal programs only)
2208 void cmd_msg3(char *cmdbuf)
2211 struct CtdlMessage *msg = NULL;
2214 if (CC->internal_pgm == 0) {
2215 cprintf("%d This command is for internal programs only.\n",
2216 ERROR + HIGHER_ACCESS_REQUIRED);
2220 msgnum = extract_long(cmdbuf, 0);
2221 msg = CtdlFetchMessage(msgnum, 1);
2223 cprintf("%d Message %ld not found.\n",
2224 ERROR + MESSAGE_NOT_FOUND, msgnum);
2228 serialize_message(&smr, msg);
2229 CtdlFreeMessage(msg);
2232 cprintf("%d Unable to serialize message\n",
2233 ERROR + INTERNAL_ERROR);
2237 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2238 client_write((char *)smr.ser, (int)smr.len);
2245 * Display a message using MIME content types
2247 void cmd_msg4(char *cmdbuf)
2252 msgid = extract_long(cmdbuf, 0);
2253 extract_token(section, cmdbuf, 1, '|', sizeof section);
2254 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2260 * Client tells us its preferred message format(s)
2262 void cmd_msgp(char *cmdbuf)
2264 if (!strcasecmp(cmdbuf, "dont_decode")) {
2265 CC->msg4_dont_decode = 1;
2266 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2269 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2270 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2276 * Open a component of a MIME message as a download file
2278 void cmd_opna(char *cmdbuf)
2281 char desired_section[128];
2283 msgid = extract_long(cmdbuf, 0);
2284 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2285 safestrncpy(CC->download_desired_section, desired_section,
2286 sizeof CC->download_desired_section);
2287 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2292 * Open a component of a MIME message and transmit it all at once
2294 void cmd_dlat(char *cmdbuf)
2297 char desired_section[128];
2299 msgid = extract_long(cmdbuf, 0);
2300 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2301 safestrncpy(CC->download_desired_section, desired_section,
2302 sizeof CC->download_desired_section);
2303 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2308 * Save one or more message pointers into a specified room
2309 * (Returns 0 for success, nonzero for failure)
2310 * roomname may be NULL to use the current room
2312 * Note that the 'supplied_msg' field may be set to NULL, in which case
2313 * the message will be fetched from disk, by number, if we need to perform
2314 * replication checks. This adds an additional database read, so if the
2315 * caller already has the message in memory then it should be supplied. (Obviously
2316 * this mode of operation only works if we're saving a single message.)
2318 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2319 int do_repl_check, struct CtdlMessage *supplied_msg)
2322 char hold_rm[ROOMNAMELEN];
2323 struct cdbdata *cdbfr;
2326 long highest_msg = 0L;
2329 struct CtdlMessage *msg = NULL;
2331 long *msgs_to_be_merged = NULL;
2332 int num_msgs_to_be_merged = 0;
2334 CtdlLogPrintf(CTDL_DEBUG,
2335 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d)\n",
2336 roomname, num_newmsgs, do_repl_check);
2338 strcpy(hold_rm, CC->room.QRname);
2341 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2342 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2343 if (num_newmsgs > 1) supplied_msg = NULL;
2345 /* Now the regular stuff */
2346 if (CtdlGetRoomLock(&CC->room,
2347 ((roomname != NULL) ? roomname : CC->room.QRname) )
2349 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2350 return(ERROR + ROOM_NOT_FOUND);
2354 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2355 num_msgs_to_be_merged = 0;
2358 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2359 if (cdbfr == NULL) {
2363 msglist = (long *) cdbfr->ptr;
2364 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2365 num_msgs = cdbfr->len / sizeof(long);
2370 /* Create a list of msgid's which were supplied by the caller, but do
2371 * not already exist in the target room. It is absolutely taboo to
2372 * have more than one reference to the same message in a room.
2374 for (i=0; i<num_newmsgs; ++i) {
2376 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2377 if (msglist[j] == newmsgidlist[i]) {
2382 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2386 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2389 * Now merge the new messages
2391 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2392 if (msglist == NULL) {
2393 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2395 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2396 num_msgs += num_msgs_to_be_merged;
2398 /* Sort the message list, so all the msgid's are in order */
2399 num_msgs = sort_msglist(msglist, num_msgs);
2401 /* Determine the highest message number */
2402 highest_msg = msglist[num_msgs - 1];
2404 /* Write it back to disk. */
2405 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2406 msglist, (int)(num_msgs * sizeof(long)));
2408 /* Free up the memory we used. */
2411 /* Update the highest-message pointer and unlock the room. */
2412 CC->room.QRhighest = highest_msg;
2413 CtdlPutRoomLock(&CC->room);
2415 /* Perform replication checks if necessary */
2416 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2417 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2419 for (i=0; i<num_msgs_to_be_merged; ++i) {
2420 msgid = msgs_to_be_merged[i];
2422 if (supplied_msg != NULL) {
2426 msg = CtdlFetchMessage(msgid, 0);
2430 ReplicationChecks(msg);
2432 /* If the message has an Exclusive ID, index that... */
2433 if (msg->cm_fields['E'] != NULL) {
2434 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2437 /* Free up the memory we may have allocated */
2438 if (msg != supplied_msg) {
2439 CtdlFreeMessage(msg);
2447 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2450 /* Submit this room for processing by hooks */
2451 PerformRoomHooks(&CC->room);
2453 /* Go back to the room we were in before we wandered here... */
2454 CtdlGetRoom(&CC->room, hold_rm);
2456 /* Bump the reference count for all messages which were merged */
2457 for (i=0; i<num_msgs_to_be_merged; ++i) {
2458 AdjRefCount(msgs_to_be_merged[i], +1);
2461 /* Free up memory... */
2462 if (msgs_to_be_merged != NULL) {
2463 free(msgs_to_be_merged);
2466 /* Return success. */
2472 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2475 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2476 int do_repl_check, struct CtdlMessage *supplied_msg)
2478 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg);
2485 * Message base operation to save a new message to the message store
2486 * (returns new message number)
2488 * This is the back end for CtdlSubmitMsg() and should not be directly
2489 * called by server-side modules.
2492 long send_message(struct CtdlMessage *msg) {
2500 /* Get a new message number */
2501 newmsgid = get_new_message_number();
2502 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2504 /* Generate an ID if we don't have one already */
2505 if (msg->cm_fields['I']==NULL) {
2506 msg->cm_fields['I'] = strdup(msgidbuf);
2509 /* If the message is big, set its body aside for storage elsewhere */
2510 if (msg->cm_fields['M'] != NULL) {
2511 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2513 holdM = msg->cm_fields['M'];
2514 msg->cm_fields['M'] = NULL;
2518 /* Serialize our data structure for storage in the database */
2519 serialize_message(&smr, msg);
2522 msg->cm_fields['M'] = holdM;
2526 cprintf("%d Unable to serialize message\n",
2527 ERROR + INTERNAL_ERROR);
2531 /* Write our little bundle of joy into the message base */
2532 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2533 smr.ser, smr.len) < 0) {
2534 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2538 cdb_store(CDB_BIGMSGS,
2548 /* Free the memory we used for the serialized message */
2551 /* Return the *local* message ID to the caller
2552 * (even if we're storing an incoming network message)
2560 * Serialize a struct CtdlMessage into the format used on disk and network.
2562 * This function loads up a "struct ser_ret" (defined in server.h) which
2563 * contains the length of the serialized message and a pointer to the
2564 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2566 void serialize_message(struct ser_ret *ret, /* return values */
2567 struct CtdlMessage *msg) /* unserialized msg */
2569 size_t wlen, fieldlen;
2571 static char *forder = FORDER;
2574 * Check for valid message format
2576 if (is_valid_message(msg) == 0) {
2577 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2584 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2585 ret->len = ret->len +
2586 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2588 ret->ser = malloc(ret->len);
2589 if (ret->ser == NULL) {
2590 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2591 (long)ret->len, strerror(errno));
2598 ret->ser[1] = msg->cm_anon_type;
2599 ret->ser[2] = msg->cm_format_type;
2602 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2603 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2604 ret->ser[wlen++] = (char)forder[i];
2605 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2606 wlen = wlen + fieldlen + 1;
2608 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2609 (long)ret->len, (long)wlen);
2616 * Serialize a struct CtdlMessage into the format used on disk and network.
2618 * This function loads up a "struct ser_ret" (defined in server.h) which
2619 * contains the length of the serialized message and a pointer to the
2620 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2622 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2623 long Siz) /* how many chars ? */
2627 static char *forder = FORDER;
2631 * Check for valid message format
2633 if (is_valid_message(msg) == 0) {
2634 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2638 buf = (char*) malloc (Siz + 1);
2642 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2643 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2644 msg->cm_fields[(int)forder[i]]);
2645 client_write (buf, strlen(buf));
2654 * Check to see if any messages already exist in the current room which
2655 * carry the same Exclusive ID as this one. If any are found, delete them.
2657 void ReplicationChecks(struct CtdlMessage *msg) {
2658 long old_msgnum = (-1L);
2660 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2662 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2665 /* No exclusive id? Don't do anything. */
2666 if (msg == NULL) return;
2667 if (msg->cm_fields['E'] == NULL) return;
2668 if (IsEmptyStr(msg->cm_fields['E'])) return;
2669 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2670 msg->cm_fields['E'], CC->room.QRname);*/
2672 old_msgnum = locate_message_by_euid(msg->cm_fields['E'], &CC->room);
2673 if (old_msgnum > 0L) {
2674 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2675 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2682 * Save a message to disk and submit it into the delivery system.
2684 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2685 struct recptypes *recps, /* recipients (if mail) */
2686 char *force, /* force a particular room? */
2687 int flags /* should the bessage be exported clean? */
2689 char submit_filename[128];
2690 char generated_timestamp[32];
2691 char hold_rm[ROOMNAMELEN];
2692 char actual_rm[ROOMNAMELEN];
2693 char force_room[ROOMNAMELEN];
2694 char content_type[SIZ]; /* We have to learn this */
2695 char recipient[SIZ];
2698 struct ctdluser userbuf;
2700 struct MetaData smi;
2701 FILE *network_fp = NULL;
2702 static int seqnum = 1;
2703 struct CtdlMessage *imsg = NULL;
2705 size_t instr_alloc = 0;
2707 char *hold_R, *hold_D;
2708 char *collected_addresses = NULL;
2709 struct addresses_to_be_filed *aptr = NULL;
2710 char *saved_rfc822_version = NULL;
2711 int qualified_for_journaling = 0;
2712 struct CitContext *CCC = CC; /* CachedCitContext - performance boost */
2713 char bounce_to[1024] = "";
2717 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2718 if (is_valid_message(msg) == 0) return(-1); /* self check */
2720 /* If this message has no timestamp, we take the liberty of
2721 * giving it one, right now.
2723 if (msg->cm_fields['T'] == NULL) {
2724 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2725 msg->cm_fields['T'] = strdup(generated_timestamp);
2728 /* If this message has no path, we generate one.
2730 if (msg->cm_fields['P'] == NULL) {
2731 if (msg->cm_fields['A'] != NULL) {
2732 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2733 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2734 if (isspace(msg->cm_fields['P'][a])) {
2735 msg->cm_fields['P'][a] = ' ';
2740 msg->cm_fields['P'] = strdup("unknown");
2744 if (force == NULL) {
2745 strcpy(force_room, "");
2748 strcpy(force_room, force);
2751 /* Learn about what's inside, because it's what's inside that counts */
2752 if (msg->cm_fields['M'] == NULL) {
2753 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2757 switch (msg->cm_format_type) {
2759 strcpy(content_type, "text/x-citadel-variformat");
2762 strcpy(content_type, "text/plain");
2765 strcpy(content_type, "text/plain");
2766 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2769 safestrncpy(content_type, &mptr[13], sizeof content_type);
2770 striplt(content_type);
2771 aptr = content_type;
2772 while (!IsEmptyStr(aptr)) {
2784 /* Goto the correct room */
2785 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2786 strcpy(hold_rm, CCC->room.QRname);
2787 strcpy(actual_rm, CCC->room.QRname);
2788 if (recps != NULL) {
2789 strcpy(actual_rm, SENTITEMS);
2792 /* If the user is a twit, move to the twit room for posting */
2794 if (CCC->user.axlevel == 2) {
2795 strcpy(hold_rm, actual_rm);
2796 strcpy(actual_rm, config.c_twitroom);
2797 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2801 /* ...or if this message is destined for Aide> then go there. */
2802 if (!IsEmptyStr(force_room)) {
2803 strcpy(actual_rm, force_room);
2806 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2807 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2808 /* CtdlGetRoom(&CCC->room, actual_rm); */
2809 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2813 * If this message has no O (room) field, generate one.
2815 if (msg->cm_fields['O'] == NULL) {
2816 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2819 /* Perform "before save" hooks (aborting if any return nonzero) */
2820 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2821 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2824 * If this message has an Exclusive ID, and the room is replication
2825 * checking enabled, then do replication checks.
2827 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2828 ReplicationChecks(msg);
2831 /* Save it to disk */
2832 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2833 newmsgid = send_message(msg);
2834 if (newmsgid <= 0L) return(-5);
2836 /* Write a supplemental message info record. This doesn't have to
2837 * be a critical section because nobody else knows about this message
2840 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2841 memset(&smi, 0, sizeof(struct MetaData));
2842 smi.meta_msgnum = newmsgid;
2843 smi.meta_refcount = 0;
2844 safestrncpy(smi.meta_content_type, content_type,
2845 sizeof smi.meta_content_type);
2848 * Measure how big this message will be when rendered as RFC822.
2849 * We do this for two reasons:
2850 * 1. We need the RFC822 length for the new metadata record, so the
2851 * POP and IMAP services don't have to calculate message lengths
2852 * while the user is waiting (multiplied by potentially hundreds
2853 * or thousands of messages).
2854 * 2. If journaling is enabled, we will need an RFC822 version of the
2855 * message to attach to the journalized copy.
2857 if (CCC->redirect_buffer != NULL) {
2858 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
2861 CCC->redirect_buffer = malloc(SIZ);
2862 CCC->redirect_len = 0;
2863 CCC->redirect_alloc = SIZ;
2864 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
2865 smi.meta_rfc822_length = CCC->redirect_len;
2866 saved_rfc822_version = CCC->redirect_buffer;
2867 CCC->redirect_buffer = NULL;
2868 CCC->redirect_len = 0;
2869 CCC->redirect_alloc = 0;
2873 /* Now figure out where to store the pointers */
2874 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
2876 /* If this is being done by the networker delivering a private
2877 * message, we want to BYPASS saving the sender's copy (because there
2878 * is no local sender; it would otherwise go to the Trashcan).
2880 if ((!CCC->internal_pgm) || (recps == NULL)) {
2881 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
2882 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
2883 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
2887 /* For internet mail, drop a copy in the outbound queue room */
2888 if ((recps != NULL) && (recps->num_internet > 0)) {
2889 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
2892 /* If other rooms are specified, drop them there too. */
2893 if ((recps != NULL) && (recps->num_room > 0))
2894 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
2895 extract_token(recipient, recps->recp_room, i,
2896 '|', sizeof recipient);
2897 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
2898 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
2901 /* Bump this user's messages posted counter. */
2902 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
2903 lgetuser(&CCC->user, CCC->curr_user);
2904 CCC->user.posted = CCC->user.posted + 1;
2905 lputuser(&CCC->user);
2907 /* Decide where bounces need to be delivered */
2908 if ((recps != NULL) && (recps->bounce_to != NULL)) {
2909 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
2911 else if (CCC->logged_in) {
2912 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
2915 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
2918 /* If this is private, local mail, make a copy in the
2919 * recipient's mailbox and bump the reference count.
2921 if ((recps != NULL) && (recps->num_local > 0))
2922 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
2923 extract_token(recipient, recps->recp_local, i,
2924 '|', sizeof recipient);
2925 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
2927 if (getuser(&userbuf, recipient) == 0) {
2928 // Add a flag so the Funambol module knows its mail
2929 msg->cm_fields['W'] = strdup(recipient);
2930 MailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
2931 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
2932 BumpNewMailCounter(userbuf.usernum);
2933 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
2934 /* Generate a instruction message for the Funambol notification
2935 * server, in the same style as the SMTP queue
2938 instr = malloc(instr_alloc);
2939 snprintf(instr, instr_alloc,
2940 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
2942 SPOOLMIME, newmsgid, (long)time(NULL),
2946 imsg = malloc(sizeof(struct CtdlMessage));
2947 memset(imsg, 0, sizeof(struct CtdlMessage));
2948 imsg->cm_magic = CTDLMESSAGE_MAGIC;
2949 imsg->cm_anon_type = MES_NORMAL;
2950 imsg->cm_format_type = FMT_RFC822;
2951 imsg->cm_fields['A'] = strdup("Citadel");
2952 imsg->cm_fields['J'] = strdup("do not journal");
2953 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
2954 imsg->cm_fields['W'] = strdup(recipient);
2955 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
2956 CtdlFreeMessage(imsg);
2960 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
2961 CtdlSaveMsgPointerInRoom(config.c_aideroom,
2966 /* Perform "after save" hooks */
2967 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
2968 PerformMessageHooks(msg, EVT_AFTERSAVE);
2970 /* For IGnet mail, we have to save a new copy into the spooler for
2971 * each recipient, with the R and D fields set to the recipient and
2972 * destination-node. This has two ugly side effects: all other
2973 * recipients end up being unlisted in this recipient's copy of the
2974 * message, and it has to deliver multiple messages to the same
2975 * node. We'll revisit this again in a year or so when everyone has
2976 * a network spool receiver that can handle the new style messages.
2978 if ((recps != NULL) && (recps->num_ignet > 0))
2979 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
2980 extract_token(recipient, recps->recp_ignet, i,
2981 '|', sizeof recipient);
2983 hold_R = msg->cm_fields['R'];
2984 hold_D = msg->cm_fields['D'];
2985 msg->cm_fields['R'] = malloc(SIZ);
2986 msg->cm_fields['D'] = malloc(128);
2987 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
2988 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
2990 serialize_message(&smr, msg);
2992 snprintf(submit_filename, sizeof submit_filename,
2993 "%s/netmail.%04lx.%04x.%04x",
2995 (long) getpid(), CCC->cs_pid, ++seqnum);
2996 network_fp = fopen(submit_filename, "wb+");
2997 if (network_fp != NULL) {
2998 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3004 free(msg->cm_fields['R']);
3005 free(msg->cm_fields['D']);
3006 msg->cm_fields['R'] = hold_R;
3007 msg->cm_fields['D'] = hold_D;
3010 /* Go back to the room we started from */
3011 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3012 if (strcasecmp(hold_rm, CCC->room.QRname))
3013 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3015 /* For internet mail, generate delivery instructions.
3016 * Yes, this is recursive. Deal with it. Infinite recursion does
3017 * not happen because the delivery instructions message does not
3018 * contain a recipient.
3020 if ((recps != NULL) && (recps->num_internet > 0)) {
3021 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3023 instr = malloc(instr_alloc);
3024 snprintf(instr, instr_alloc,
3025 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3027 SPOOLMIME, newmsgid, (long)time(NULL),
3031 if (recps->envelope_from != NULL) {
3032 tmp = strlen(instr);
3033 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3036 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3037 tmp = strlen(instr);
3038 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3039 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3040 instr_alloc = instr_alloc * 2;
3041 instr = realloc(instr, instr_alloc);
3043 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3046 imsg = malloc(sizeof(struct CtdlMessage));
3047 memset(imsg, 0, sizeof(struct CtdlMessage));
3048 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3049 imsg->cm_anon_type = MES_NORMAL;
3050 imsg->cm_format_type = FMT_RFC822;
3051 imsg->cm_fields['A'] = strdup("Citadel");
3052 imsg->cm_fields['J'] = strdup("do not journal");
3053 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3054 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3055 CtdlFreeMessage(imsg);
3059 * Any addresses to harvest for someone's address book?
3061 if ( (CCC->logged_in) && (recps != NULL) ) {
3062 collected_addresses = harvest_collected_addresses(msg);
3065 if (collected_addresses != NULL) {
3066 aptr = (struct addresses_to_be_filed *)
3067 malloc(sizeof(struct addresses_to_be_filed));
3068 MailboxName(actual_rm, sizeof actual_rm,
3069 &CCC->user, USERCONTACTSROOM);
3070 aptr->roomname = strdup(actual_rm);
3071 aptr->collected_addresses = collected_addresses;
3072 begin_critical_section(S_ATBF);
3075 end_critical_section(S_ATBF);
3079 * Determine whether this message qualifies for journaling.
3081 if (msg->cm_fields['J'] != NULL) {
3082 qualified_for_journaling = 0;
3085 if (recps == NULL) {
3086 qualified_for_journaling = config.c_journal_pubmsgs;
3088 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3089 qualified_for_journaling = config.c_journal_email;
3092 qualified_for_journaling = config.c_journal_pubmsgs;
3097 * Do we have to perform journaling? If so, hand off the saved
3098 * RFC822 version will be handed off to the journaler for background
3099 * submit. Otherwise, we have to free the memory ourselves.
3101 if (saved_rfc822_version != NULL) {
3102 if (qualified_for_journaling) {
3103 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3106 free(saved_rfc822_version);
3116 void aide_message (char *text, char *subject)
3118 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3123 * Convenience function for generating small administrative messages.
3125 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3126 int format_type, const char *subject)
3128 struct CtdlMessage *msg;
3129 struct recptypes *recp = NULL;
3131 msg = malloc(sizeof(struct CtdlMessage));
3132 memset(msg, 0, sizeof(struct CtdlMessage));
3133 msg->cm_magic = CTDLMESSAGE_MAGIC;
3134 msg->cm_anon_type = MES_NORMAL;
3135 msg->cm_format_type = format_type;
3138 msg->cm_fields['A'] = strdup(from);
3140 else if (fromaddr != NULL) {
3141 msg->cm_fields['A'] = strdup(fromaddr);
3142 if (strchr(msg->cm_fields['A'], '@')) {
3143 *strchr(msg->cm_fields['A'], '@') = 0;
3147 msg->cm_fields['A'] = strdup("Citadel");
3150 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3151 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3152 msg->cm_fields['N'] = strdup(NODENAME);
3154 msg->cm_fields['R'] = strdup(to);
3155 recp = validate_recipients(to, NULL, 0);
3157 if (subject != NULL) {
3158 msg->cm_fields['U'] = strdup(subject);
3160 msg->cm_fields['M'] = strdup(text);
3162 CtdlSubmitMsg(msg, recp, room, 0);
3163 CtdlFreeMessage(msg);
3164 if (recp != NULL) free_recipients(recp);
3170 * Back end function used by CtdlMakeMessage() and similar functions
3172 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3173 size_t maxlen, /* maximum message length */
3174 char *exist, /* if non-null, append to it;
3175 exist is ALWAYS freed */
3176 int crlf, /* CRLF newlines instead of LF */
3177 int sock /* socket handle or 0 for this session's client socket */
3181 size_t message_len = 0;
3182 size_t buffer_len = 0;
3189 if (exist == NULL) {
3196 message_len = strlen(exist);
3197 buffer_len = message_len + 4096;
3198 m = realloc(exist, buffer_len);
3205 /* Do we need to change leading ".." to "." for SMTP escaping? */
3206 if (!strcmp(terminator, ".")) {
3210 /* flush the input if we have nowhere to store it */
3215 /* read in the lines of message text one by one */
3218 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3221 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3223 if (!strcmp(buf, terminator)) finished = 1;
3225 strcat(buf, "\r\n");
3231 /* Unescape SMTP-style input of two dots at the beginning of the line */
3233 if (!strncmp(buf, "..", 2)) {
3234 strcpy(buf, &buf[1]);
3238 if ( (!flushing) && (!finished) ) {
3239 /* Measure the line */
3240 linelen = strlen(buf);
3242 /* augment the buffer if we have to */
3243 if ((message_len + linelen) >= buffer_len) {
3244 ptr = realloc(m, (buffer_len * 2) );
3245 if (ptr == NULL) { /* flush if can't allocate */
3248 buffer_len = (buffer_len * 2);
3250 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3254 /* Add the new line to the buffer. NOTE: this loop must avoid
3255 * using functions like strcat() and strlen() because they
3256 * traverse the entire buffer upon every call, and doing that
3257 * for a multi-megabyte message slows it down beyond usability.
3259 strcpy(&m[message_len], buf);
3260 message_len += linelen;
3263 /* if we've hit the max msg length, flush the rest */
3264 if (message_len >= maxlen) flushing = 1;
3266 } while (!finished);
3274 * Build a binary message to be saved on disk.
3275 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3276 * will become part of the message. This means you are no longer
3277 * responsible for managing that memory -- it will be freed along with
3278 * the rest of the fields when CtdlFreeMessage() is called.)
3281 struct CtdlMessage *CtdlMakeMessage(
3282 struct ctdluser *author, /* author's user structure */
3283 char *recipient, /* NULL if it's not mail */
3284 char *recp_cc, /* NULL if it's not mail */
3285 char *room, /* room where it's going */
3286 int type, /* see MES_ types in header file */
3287 int format_type, /* variformat, plain text, MIME... */
3288 char *fake_name, /* who we're masquerading as */
3289 char *my_email, /* which of my email addresses to use (empty is ok) */
3290 char *subject, /* Subject (optional) */
3291 char *supplied_euid, /* ...or NULL if this is irrelevant */
3292 char *preformatted_text, /* ...or NULL to read text from client */
3293 char *references /* Thread references */
3295 char dest_node[256];
3297 struct CtdlMessage *msg;
3299 msg = malloc(sizeof(struct CtdlMessage));
3300 memset(msg, 0, sizeof(struct CtdlMessage));
3301 msg->cm_magic = CTDLMESSAGE_MAGIC;
3302 msg->cm_anon_type = type;
3303 msg->cm_format_type = format_type;
3305 /* Don't confuse the poor folks if it's not routed mail. */
3306 strcpy(dest_node, "");
3308 if (recipient != NULL) striplt(recipient);
3309 if (recp_cc != NULL) striplt(recp_cc);
3311 /* Path or Return-Path */
3312 if (my_email == NULL) my_email = "";
3314 if (!IsEmptyStr(my_email)) {
3315 msg->cm_fields['P'] = strdup(my_email);
3318 snprintf(buf, sizeof buf, "%s", author->fullname);
3319 msg->cm_fields['P'] = strdup(buf);
3321 convert_spaces_to_underscores(msg->cm_fields['P']);
3323 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3324 msg->cm_fields['T'] = strdup(buf);
3326 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3327 msg->cm_fields['A'] = strdup(fake_name);
3330 msg->cm_fields['A'] = strdup(author->fullname);
3333 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3334 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3337 msg->cm_fields['O'] = strdup(CC->room.QRname);
3340 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3341 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3343 if ((recipient != NULL) && (recipient[0] != 0)) {
3344 msg->cm_fields['R'] = strdup(recipient);
3346 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3347 msg->cm_fields['Y'] = strdup(recp_cc);
3349 if (dest_node[0] != 0) {
3350 msg->cm_fields['D'] = strdup(dest_node);
3353 if (!IsEmptyStr(my_email)) {
3354 msg->cm_fields['F'] = strdup(my_email);
3356 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3357 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3360 if (subject != NULL) {
3363 length = strlen(subject);
3369 while ((subject[i] != '\0') &&
3370 (IsAscii = isascii(subject[i]) != 0 ))
3373 msg->cm_fields['U'] = strdup(subject);
3374 else /* ok, we've got utf8 in the string. */
3376 msg->cm_fields['U'] = rfc2047encode(subject, length);
3382 if (supplied_euid != NULL) {
3383 msg->cm_fields['E'] = strdup(supplied_euid);
3386 if (references != NULL) {
3387 if (!IsEmptyStr(references)) {
3388 msg->cm_fields['W'] = strdup(references);
3392 if (preformatted_text != NULL) {
3393 msg->cm_fields['M'] = preformatted_text;
3396 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3404 * Check to see whether we have permission to post a message in the current
3405 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3406 * returns 0 on success.
3408 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3410 const char* RemoteIdentifier,
3414 if (!(CC->logged_in) &&
3415 (PostPublic == POST_LOGGED_IN)) {
3416 snprintf(errmsgbuf, n, "Not logged in.");
3417 return (ERROR + NOT_LOGGED_IN);
3419 else if (PostPublic == CHECK_EXISTANCE) {
3420 return (0); // We're Evaling whether a recipient exists
3422 else if (!(CC->logged_in)) {
3424 if ((CC->room.QRflags & QR_READONLY)) {
3425 snprintf(errmsgbuf, n, "Not logged in.");
3426 return (ERROR + NOT_LOGGED_IN);
3428 if (CC->room.QRflags2 & QR2_MODERATED) {
3429 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3430 return (ERROR + NOT_LOGGED_IN);
3432 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3437 if (RemoteIdentifier == NULL)
3439 snprintf(errmsgbuf, n, "Need sender to permit access.");
3440 return (ERROR + USERNAME_REQUIRED);
3443 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3444 begin_critical_section(S_NETCONFIGS);
3445 if (!read_spoolcontrol_file(&sc, filename))
3447 end_critical_section(S_NETCONFIGS);
3448 snprintf(errmsgbuf, n,
3449 "This mailing list only accepts posts from subscribers.");
3450 return (ERROR + NO_SUCH_USER);
3452 end_critical_section(S_NETCONFIGS);
3453 found = is_recipient (sc, RemoteIdentifier);
3454 free_spoolcontrol_struct(&sc);
3459 snprintf(errmsgbuf, n,
3460 "This mailing list only accepts posts from subscribers.");
3461 return (ERROR + NO_SUCH_USER);
3468 if ((CC->user.axlevel < 2)
3469 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3470 snprintf(errmsgbuf, n, "Need to be validated to enter "
3471 "(except in %s> to sysop)", MAILROOM);
3472 return (ERROR + HIGHER_ACCESS_REQUIRED);
3475 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3476 if (!(ra & UA_POSTALLOWED)) {
3477 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3478 return (ERROR + HIGHER_ACCESS_REQUIRED);
3481 strcpy(errmsgbuf, "Ok");
3487 * Check to see if the specified user has Internet mail permission
3488 * (returns nonzero if permission is granted)
3490 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3492 /* Do not allow twits to send Internet mail */
3493 if (who->axlevel <= 2) return(0);
3495 /* Globally enabled? */
3496 if (config.c_restrict == 0) return(1);
3498 /* User flagged ok? */
3499 if (who->flags & US_INTERNET) return(2);
3501 /* Aide level access? */
3502 if (who->axlevel >= 6) return(3);
3504 /* No mail for you! */
3510 * Validate recipients, count delivery types and errors, and handle aliasing
3511 * FIXME check for dupes!!!!!
3513 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3514 * were specified, or the number of addresses found invalid.
3516 * Caller needs to free the result using free_recipients()
3518 struct recptypes *validate_recipients(char *supplied_recipients,
3519 const char *RemoteIdentifier,
3521 struct recptypes *ret;
3522 char *recipients = NULL;
3523 char this_recp[256];
3524 char this_recp_cooked[256];
3530 struct ctdluser tempUS;
3531 struct ctdlroom tempQR;
3532 struct ctdlroom tempQR2;
3538 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3539 if (ret == NULL) return(NULL);
3541 /* Set all strings to null and numeric values to zero */
3542 memset(ret, 0, sizeof(struct recptypes));
3544 if (supplied_recipients == NULL) {
3545 recipients = strdup("");
3548 recipients = strdup(supplied_recipients);
3551 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3552 * actually need, but it's healthier for the heap than doing lots of tiny
3553 * realloc() calls instead.
3556 ret->errormsg = malloc(strlen(recipients) + 1024);
3557 ret->recp_local = malloc(strlen(recipients) + 1024);
3558 ret->recp_internet = malloc(strlen(recipients) + 1024);
3559 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3560 ret->recp_room = malloc(strlen(recipients) + 1024);
3561 ret->display_recp = malloc(strlen(recipients) + 1024);
3563 ret->errormsg[0] = 0;
3564 ret->recp_local[0] = 0;
3565 ret->recp_internet[0] = 0;
3566 ret->recp_ignet[0] = 0;
3567 ret->recp_room[0] = 0;
3568 ret->display_recp[0] = 0;
3570 ret->recptypes_magic = RECPTYPES_MAGIC;
3572 /* Change all valid separator characters to commas */
3573 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3574 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3575 recipients[i] = ',';
3579 /* Now start extracting recipients... */
3581 while (!IsEmptyStr(recipients)) {
3583 for (i=0; i<=strlen(recipients); ++i) {
3584 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3585 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3586 safestrncpy(this_recp, recipients, i+1);
3588 if (recipients[i] == ',') {
3589 strcpy(recipients, &recipients[i+1]);
3592 strcpy(recipients, "");
3599 if (IsEmptyStr(this_recp))
3601 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3603 mailtype = alias(this_recp);
3604 mailtype = alias(this_recp);
3605 mailtype = alias(this_recp);
3607 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3608 if (this_recp[j]=='_') {
3609 this_recp_cooked[j] = ' ';
3612 this_recp_cooked[j] = this_recp[j];
3615 this_recp_cooked[j] = '\0';
3620 if (!strcasecmp(this_recp, "sysop")) {
3622 strcpy(this_recp, config.c_aideroom);
3623 if (!IsEmptyStr(ret->recp_room)) {
3624 strcat(ret->recp_room, "|");
3626 strcat(ret->recp_room, this_recp);
3628 else if ( (!strncasecmp(this_recp, "room_", 5))
3629 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3631 /* Save room so we can restore it later */
3635 /* Check permissions to send mail to this room */
3636 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3648 if (!IsEmptyStr(ret->recp_room)) {
3649 strcat(ret->recp_room, "|");
3651 strcat(ret->recp_room, &this_recp_cooked[5]);
3654 /* Restore room in case something needs it */
3658 else if (getuser(&tempUS, this_recp) == 0) {
3660 strcpy(this_recp, tempUS.fullname);
3661 if (!IsEmptyStr(ret->recp_local)) {
3662 strcat(ret->recp_local, "|");
3664 strcat(ret->recp_local, this_recp);
3666 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3668 strcpy(this_recp, tempUS.fullname);
3669 if (!IsEmptyStr(ret->recp_local)) {
3670 strcat(ret->recp_local, "|");
3672 strcat(ret->recp_local, this_recp);
3680 /* Yes, you're reading this correctly: if the target
3681 * domain points back to the local system or an attached
3682 * Citadel directory, the address is invalid. That's
3683 * because if the address were valid, we would have
3684 * already translated it to a local address by now.
3686 if (IsDirectory(this_recp, 0)) {
3691 ++ret->num_internet;
3692 if (!IsEmptyStr(ret->recp_internet)) {
3693 strcat(ret->recp_internet, "|");
3695 strcat(ret->recp_internet, this_recp);
3700 if (!IsEmptyStr(ret->recp_ignet)) {
3701 strcat(ret->recp_ignet, "|");
3703 strcat(ret->recp_ignet, this_recp);
3711 if (IsEmptyStr(errmsg)) {
3712 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3715 snprintf(append, sizeof append, "%s", errmsg);
3717 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3718 if (!IsEmptyStr(ret->errormsg)) {
3719 strcat(ret->errormsg, "; ");
3721 strcat(ret->errormsg, append);
3725 if (IsEmptyStr(ret->display_recp)) {
3726 strcpy(append, this_recp);
3729 snprintf(append, sizeof append, ", %s", this_recp);
3731 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3732 strcat(ret->display_recp, append);
3737 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3738 ret->num_room + ret->num_error) == 0) {
3739 ret->num_error = (-1);
3740 strcpy(ret->errormsg, "No recipients specified.");
3743 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3744 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3745 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3746 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3747 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3748 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3756 * Destructor for struct recptypes
3758 void free_recipients(struct recptypes *valid) {
3760 if (valid == NULL) {
3764 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3765 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3769 if (valid->errormsg != NULL) free(valid->errormsg);
3770 if (valid->recp_local != NULL) free(valid->recp_local);
3771 if (valid->recp_internet != NULL) free(valid->recp_internet);
3772 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3773 if (valid->recp_room != NULL) free(valid->recp_room);
3774 if (valid->display_recp != NULL) free(valid->display_recp);
3775 if (valid->bounce_to != NULL) free(valid->bounce_to);
3776 if (valid->envelope_from != NULL) free(valid->envelope_from);
3783 * message entry - mode 0 (normal)
3785 void cmd_ent0(char *entargs)
3791 char supplied_euid[128];
3793 int format_type = 0;
3794 char newusername[256];
3795 char newuseremail[256];
3796 struct CtdlMessage *msg;
3800 struct recptypes *valid = NULL;
3801 struct recptypes *valid_to = NULL;
3802 struct recptypes *valid_cc = NULL;
3803 struct recptypes *valid_bcc = NULL;
3805 int subject_required = 0;
3810 int newuseremail_ok = 0;
3811 char references[SIZ];
3816 post = extract_int(entargs, 0);
3817 extract_token(recp, entargs, 1, '|', sizeof recp);
3818 anon_flag = extract_int(entargs, 2);
3819 format_type = extract_int(entargs, 3);
3820 extract_token(subject, entargs, 4, '|', sizeof subject);
3821 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3822 do_confirm = extract_int(entargs, 6);
3823 extract_token(cc, entargs, 7, '|', sizeof cc);
3824 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3825 switch(CC->room.QRdefaultview) {
3828 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3831 supplied_euid[0] = 0;
3834 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3835 extract_token(references, entargs, 11, '|', sizeof references);
3836 for (ptr=references; *ptr != 0; ++ptr) {
3837 if (*ptr == '!') *ptr = '|';
3840 /* first check to make sure the request is valid. */
3842 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3845 cprintf("%d %s\n", err, errmsg);
3849 /* Check some other permission type things. */
3851 if (IsEmptyStr(newusername)) {
3852 strcpy(newusername, CC->user.fullname);
3854 if ( (CC->user.axlevel < 6)
3855 && (strcasecmp(newusername, CC->user.fullname))
3856 && (strcasecmp(newusername, CC->cs_inet_fn))
3858 cprintf("%d You don't have permission to author messages as '%s'.\n",
3859 ERROR + HIGHER_ACCESS_REQUIRED,
3866 if (IsEmptyStr(newuseremail)) {
3867 newuseremail_ok = 1;
3870 if (!IsEmptyStr(newuseremail)) {
3871 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3872 newuseremail_ok = 1;
3874 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3875 j = num_tokens(CC->cs_inet_other_emails, '|');
3876 for (i=0; i<j; ++i) {
3877 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3878 if (!strcasecmp(newuseremail, buf)) {
3879 newuseremail_ok = 1;
3885 if (!newuseremail_ok) {
3886 cprintf("%d You don't have permission to author messages as '%s'.\n",
3887 ERROR + HIGHER_ACCESS_REQUIRED,
3893 CC->cs_flags |= CS_POSTING;
3895 /* In mailbox rooms we have to behave a little differently --
3896 * make sure the user has specified at least one recipient. Then
3897 * validate the recipient(s). We do this for the Mail> room, as
3898 * well as any room which has the "Mailbox" view set.
3901 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3902 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3904 if (CC->user.axlevel < 2) {
3905 strcpy(recp, "sysop");
3910 valid_to = validate_recipients(recp, NULL, 0);
3911 if (valid_to->num_error > 0) {
3912 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3913 free_recipients(valid_to);
3917 valid_cc = validate_recipients(cc, NULL, 0);
3918 if (valid_cc->num_error > 0) {
3919 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3920 free_recipients(valid_to);
3921 free_recipients(valid_cc);
3925 valid_bcc = validate_recipients(bcc, NULL, 0);
3926 if (valid_bcc->num_error > 0) {
3927 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3928 free_recipients(valid_to);
3929 free_recipients(valid_cc);
3930 free_recipients(valid_bcc);
3934 /* Recipient required, but none were specified */
3935 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3936 free_recipients(valid_to);
3937 free_recipients(valid_cc);
3938 free_recipients(valid_bcc);
3939 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3943 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3944 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3945 cprintf("%d You do not have permission "
3946 "to send Internet mail.\n",
3947 ERROR + HIGHER_ACCESS_REQUIRED);
3948 free_recipients(valid_to);
3949 free_recipients(valid_cc);
3950 free_recipients(valid_bcc);
3955 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)
3956 && (CC->user.axlevel < 4) ) {
3957 cprintf("%d Higher access required for network mail.\n",
3958 ERROR + HIGHER_ACCESS_REQUIRED);
3959 free_recipients(valid_to);
3960 free_recipients(valid_cc);
3961 free_recipients(valid_bcc);
3965 if ((RESTRICT_INTERNET == 1)
3966 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3967 && ((CC->user.flags & US_INTERNET) == 0)
3968 && (!CC->internal_pgm)) {
3969 cprintf("%d You don't have access to Internet mail.\n",
3970 ERROR + HIGHER_ACCESS_REQUIRED);
3971 free_recipients(valid_to);
3972 free_recipients(valid_cc);
3973 free_recipients(valid_bcc);
3979 /* Is this a room which has anonymous-only or anonymous-option? */
3980 anonymous = MES_NORMAL;
3981 if (CC->room.QRflags & QR_ANONONLY) {
3982 anonymous = MES_ANONONLY;
3984 if (CC->room.QRflags & QR_ANONOPT) {
3985 if (anon_flag == 1) { /* only if the user requested it */
3986 anonymous = MES_ANONOPT;
3990 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3994 /* Recommend to the client that the use of a message subject is
3995 * strongly recommended in this room, if either the SUBJECTREQ flag
3996 * is set, or if there is one or more Internet email recipients.
3998 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3999 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4000 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4001 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4003 /* If we're only checking the validity of the request, return
4004 * success without creating the message.
4007 cprintf("%d %s|%d\n", CIT_OK,
4008 ((valid_to != NULL) ? valid_to->display_recp : ""),
4010 free_recipients(valid_to);
4011 free_recipients(valid_cc);
4012 free_recipients(valid_bcc);
4016 /* We don't need these anymore because we'll do it differently below */
4017 free_recipients(valid_to);
4018 free_recipients(valid_cc);
4019 free_recipients(valid_bcc);
4021 /* Read in the message from the client. */
4023 cprintf("%d send message\n", START_CHAT_MODE);
4025 cprintf("%d send message\n", SEND_LISTING);
4028 msg = CtdlMakeMessage(&CC->user, recp, cc,
4029 CC->room.QRname, anonymous, format_type,
4030 newusername, newuseremail, subject,
4031 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4034 /* Put together one big recipients struct containing to/cc/bcc all in
4035 * one. This is for the envelope.
4037 char *all_recps = malloc(SIZ * 3);
4038 strcpy(all_recps, recp);
4039 if (!IsEmptyStr(cc)) {
4040 if (!IsEmptyStr(all_recps)) {
4041 strcat(all_recps, ",");
4043 strcat(all_recps, cc);
4045 if (!IsEmptyStr(bcc)) {
4046 if (!IsEmptyStr(all_recps)) {
4047 strcat(all_recps, ",");
4049 strcat(all_recps, bcc);
4051 if (!IsEmptyStr(all_recps)) {
4052 valid = validate_recipients(all_recps, NULL, 0);
4060 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4063 cprintf("%ld\n", msgnum);
4065 cprintf("Message accepted.\n");
4068 cprintf("Internal error.\n");
4070 if (msg->cm_fields['E'] != NULL) {
4071 cprintf("%s\n", msg->cm_fields['E']);
4078 CtdlFreeMessage(msg);
4080 if (valid != NULL) {
4081 free_recipients(valid);
4089 * API function to delete messages which match a set of criteria
4090 * (returns the actual number of messages deleted)
4092 int CtdlDeleteMessages(char *room_name, /* which room */
4093 long *dmsgnums, /* array of msg numbers to be deleted */
4094 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4095 char *content_type /* or "" for any. regular expressions expected. */
4098 struct ctdlroom qrbuf;
4099 struct cdbdata *cdbfr;
4100 long *msglist = NULL;
4101 long *dellist = NULL;
4104 int num_deleted = 0;
4106 struct MetaData smi;
4109 int need_to_free_re = 0;
4111 if (content_type) if (!IsEmptyStr(content_type)) {
4112 regcomp(&re, content_type, 0);
4113 need_to_free_re = 1;
4115 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4116 room_name, num_dmsgnums, content_type);
4118 /* get room record, obtaining a lock... */
4119 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4120 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4122 if (need_to_free_re) regfree(&re);
4123 return (0); /* room not found */
4125 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4127 if (cdbfr != NULL) {
4128 dellist = malloc(cdbfr->len);
4129 msglist = (long *) cdbfr->ptr;
4130 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4131 num_msgs = cdbfr->len / sizeof(long);
4135 for (i = 0; i < num_msgs; ++i) {
4138 /* Set/clear a bit for each criterion */
4140 /* 0 messages in the list or a null list means that we are
4141 * interested in deleting any messages which meet the other criteria.
4143 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4144 delete_this |= 0x01;
4147 for (j=0; j<num_dmsgnums; ++j) {
4148 if (msglist[i] == dmsgnums[j]) {
4149 delete_this |= 0x01;
4154 if (IsEmptyStr(content_type)) {
4155 delete_this |= 0x02;
4157 GetMetaData(&smi, msglist[i]);
4158 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4159 delete_this |= 0x02;
4163 /* Delete message only if all bits are set */
4164 if (delete_this == 0x03) {
4165 dellist[num_deleted++] = msglist[i];
4170 num_msgs = sort_msglist(msglist, num_msgs);
4171 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4172 msglist, (int)(num_msgs * sizeof(long)));
4174 qrbuf.QRhighest = msglist[num_msgs - 1];
4176 CtdlPutRoomLock(&qrbuf);
4178 /* Go through the messages we pulled out of the index, and decrement
4179 * their reference counts by 1. If this is the only room the message
4180 * was in, the reference count will reach zero and the message will
4181 * automatically be deleted from the database. We do this in a
4182 * separate pass because there might be plug-in hooks getting called,
4183 * and we don't want that happening during an S_ROOMS critical
4186 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4187 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4188 AdjRefCount(dellist[i], -1);
4191 /* Now free the memory we used, and go away. */
4192 if (msglist != NULL) free(msglist);
4193 if (dellist != NULL) free(dellist);
4194 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4195 if (need_to_free_re) regfree(&re);
4196 return (num_deleted);
4202 * Check whether the current user has permission to delete messages from
4203 * the current room (returns 1 for yes, 0 for no)
4205 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4207 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4208 if (ra & UA_DELETEALLOWED) return(1);
4216 * Delete message from current room
4218 void cmd_dele(char *args)
4227 extract_token(msgset, args, 0, '|', sizeof msgset);
4228 num_msgs = num_tokens(msgset, ',');
4230 cprintf("%d Nothing to do.\n", CIT_OK);
4234 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4235 cprintf("%d Higher access required.\n",
4236 ERROR + HIGHER_ACCESS_REQUIRED);
4241 * Build our message set to be moved/copied
4243 msgs = malloc(num_msgs * sizeof(long));
4244 for (i=0; i<num_msgs; ++i) {
4245 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4246 msgs[i] = atol(msgtok);
4249 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4253 cprintf("%d %d message%s deleted.\n", CIT_OK,
4254 num_deleted, ((num_deleted != 1) ? "s" : ""));
4256 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4264 * move or copy a message to another room
4266 void cmd_move(char *args)
4273 char targ[ROOMNAMELEN];
4274 struct ctdlroom qtemp;
4281 extract_token(msgset, args, 0, '|', sizeof msgset);
4282 num_msgs = num_tokens(msgset, ',');
4284 cprintf("%d Nothing to do.\n", CIT_OK);
4288 extract_token(targ, args, 1, '|', sizeof targ);
4289 convert_room_name_macros(targ, sizeof targ);
4290 targ[ROOMNAMELEN - 1] = 0;
4291 is_copy = extract_int(args, 2);
4293 if (CtdlGetRoom(&qtemp, targ) != 0) {
4294 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4298 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4299 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4303 getuser(&CC->user, CC->curr_user);
4304 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4306 /* Check for permission to perform this operation.
4307 * Remember: "CC->room" is source, "qtemp" is target.
4311 /* Aides can move/copy */
4312 if (CC->user.axlevel >= 6) permit = 1;
4314 /* Room aides can move/copy */
4315 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4317 /* Permit move/copy from personal rooms */
4318 if ((CC->room.QRflags & QR_MAILBOX)
4319 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4321 /* Permit only copy from public to personal room */
4323 && (!(CC->room.QRflags & QR_MAILBOX))
4324 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4326 /* Permit message removal from collaborative delete rooms */
4327 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4329 /* Users allowed to post into the target room may move into it too. */
4330 if ((CC->room.QRflags & QR_MAILBOX) &&
4331 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4333 /* User must have access to target room */
4334 if (!(ra & UA_KNOWN)) permit = 0;
4337 cprintf("%d Higher access required.\n",
4338 ERROR + HIGHER_ACCESS_REQUIRED);
4343 * Build our message set to be moved/copied
4345 msgs = malloc(num_msgs * sizeof(long));
4346 for (i=0; i<num_msgs; ++i) {
4347 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4348 msgs[i] = atol(msgtok);
4354 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4356 cprintf("%d Cannot store message(s) in %s: error %d\n",
4362 /* Now delete the message from the source room,
4363 * if this is a 'move' rather than a 'copy' operation.
4366 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4370 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4376 * GetMetaData() - Get the supplementary record for a message
4378 void GetMetaData(struct MetaData *smibuf, long msgnum)
4381 struct cdbdata *cdbsmi;
4384 memset(smibuf, 0, sizeof(struct MetaData));
4385 smibuf->meta_msgnum = msgnum;
4386 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4388 /* Use the negative of the message number for its supp record index */
4389 TheIndex = (0L - msgnum);
4391 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4392 if (cdbsmi == NULL) {
4393 return; /* record not found; go with defaults */
4395 memcpy(smibuf, cdbsmi->ptr,
4396 ((cdbsmi->len > sizeof(struct MetaData)) ?
4397 sizeof(struct MetaData) : cdbsmi->len));
4404 * PutMetaData() - (re)write supplementary record for a message
4406 void PutMetaData(struct MetaData *smibuf)
4410 /* Use the negative of the message number for the metadata db index */
4411 TheIndex = (0L - smibuf->meta_msgnum);
4413 cdb_store(CDB_MSGMAIN,
4414 &TheIndex, (int)sizeof(long),
4415 smibuf, (int)sizeof(struct MetaData));
4420 * AdjRefCount - submit an adjustment to the reference count for a message.
4421 * (These are just queued -- we actually process them later.)
4423 void AdjRefCount(long msgnum, int incr)
4425 struct arcq new_arcq;
4428 begin_critical_section(S_SUPPMSGMAIN);
4429 if (arcfp == NULL) {
4430 arcfp = fopen(file_arcq, "ab+");
4432 end_critical_section(S_SUPPMSGMAIN);
4434 /* msgnum < 0 means that we're trying to close the file */
4436 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4437 begin_critical_section(S_SUPPMSGMAIN);
4438 if (arcfp != NULL) {
4442 end_critical_section(S_SUPPMSGMAIN);
4447 * If we can't open the queue, perform the operation synchronously.
4449 if (arcfp == NULL) {
4450 TDAP_AdjRefCount(msgnum, incr);
4454 new_arcq.arcq_msgnum = msgnum;
4455 new_arcq.arcq_delta = incr;
4456 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4464 * TDAP_ProcessAdjRefCountQueue()
4466 * Process the queue of message count adjustments that was created by calls
4467 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4468 * for each one. This should be an "off hours" operation.
4470 int TDAP_ProcessAdjRefCountQueue(void)
4472 char file_arcq_temp[PATH_MAX];
4475 struct arcq arcq_rec;
4476 int num_records_processed = 0;
4478 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4480 begin_critical_section(S_SUPPMSGMAIN);
4481 if (arcfp != NULL) {
4486 r = link(file_arcq, file_arcq_temp);
4488 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4489 end_critical_section(S_SUPPMSGMAIN);
4490 return(num_records_processed);
4494 end_critical_section(S_SUPPMSGMAIN);
4496 fp = fopen(file_arcq_temp, "rb");
4498 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4499 return(num_records_processed);
4502 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4503 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4504 ++num_records_processed;
4508 r = unlink(file_arcq_temp);
4510 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4513 return(num_records_processed);
4519 * TDAP_AdjRefCount - adjust the reference count for a message.
4520 * This one does it "for real" because it's called by
4521 * the autopurger function that processes the queue
4522 * created by AdjRefCount(). If a message's reference
4523 * count becomes zero, we also delete the message from
4524 * disk and de-index it.
4526 void TDAP_AdjRefCount(long msgnum, int incr)
4529 struct MetaData smi;
4532 /* This is a *tight* critical section; please keep it that way, as
4533 * it may get called while nested in other critical sections.
4534 * Complicating this any further will surely cause deadlock!
4536 begin_critical_section(S_SUPPMSGMAIN);
4537 GetMetaData(&smi, msgnum);
4538 smi.meta_refcount += incr;
4540 end_critical_section(S_SUPPMSGMAIN);
4541 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4542 msgnum, incr, smi.meta_refcount);
4544 /* If the reference count is now zero, delete the message
4545 * (and its supplementary record as well).
4547 if (smi.meta_refcount == 0) {
4548 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4550 /* Call delete hooks with NULL room to show it has gone altogether */
4551 PerformDeleteHooks(NULL, msgnum);
4553 /* Remove from message base */
4555 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4556 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4558 /* Remove metadata record */
4559 delnum = (0L - msgnum);
4560 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4566 * Write a generic object to this room
4568 * Note: this could be much more efficient. Right now we use two temporary
4569 * files, and still pull the message into memory as with all others.
4571 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4572 char *content_type, /* MIME type of this object */
4573 char *raw_message, /* Data to be written */
4574 off_t raw_length, /* Size of raw_message */
4575 struct ctdluser *is_mailbox, /* Mailbox room? */
4576 int is_binary, /* Is encoding necessary? */
4577 int is_unique, /* Del others of this type? */
4578 unsigned int flags /* Internal save flags */
4582 struct ctdlroom qrbuf;
4583 char roomname[ROOMNAMELEN];
4584 struct CtdlMessage *msg;
4585 char *encoded_message = NULL;
4587 if (is_mailbox != NULL) {
4588 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4591 safestrncpy(roomname, req_room, sizeof(roomname));
4594 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4597 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4600 encoded_message = malloc((size_t)(raw_length + 4096));
4603 sprintf(encoded_message, "Content-type: %s\n", content_type);
4606 sprintf(&encoded_message[strlen(encoded_message)],
4607 "Content-transfer-encoding: base64\n\n"
4611 sprintf(&encoded_message[strlen(encoded_message)],
4612 "Content-transfer-encoding: 7bit\n\n"
4618 &encoded_message[strlen(encoded_message)],
4626 &encoded_message[strlen(encoded_message)],
4632 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4633 msg = malloc(sizeof(struct CtdlMessage));
4634 memset(msg, 0, sizeof(struct CtdlMessage));
4635 msg->cm_magic = CTDLMESSAGE_MAGIC;
4636 msg->cm_anon_type = MES_NORMAL;
4637 msg->cm_format_type = 4;
4638 msg->cm_fields['A'] = strdup(CC->user.fullname);
4639 msg->cm_fields['O'] = strdup(req_room);
4640 msg->cm_fields['N'] = strdup(config.c_nodename);
4641 msg->cm_fields['H'] = strdup(config.c_humannode);
4642 msg->cm_flags = flags;
4644 msg->cm_fields['M'] = encoded_message;
4646 /* Create the requested room if we have to. */
4647 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4648 CtdlCreateRoom(roomname,
4649 ( (is_mailbox != NULL) ? 5 : 3 ),
4650 "", 0, 1, 0, VIEW_BBS);
4652 /* If the caller specified this object as unique, delete all
4653 * other objects of this type that are currently in the room.
4656 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4657 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4660 /* Now write the data */
4661 CtdlSubmitMsg(msg, NULL, roomname, 0);
4662 CtdlFreeMessage(msg);
4670 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4671 config_msgnum = msgnum;
4675 char *CtdlGetSysConfig(char *sysconfname) {
4676 char hold_rm[ROOMNAMELEN];
4679 struct CtdlMessage *msg;
4682 strcpy(hold_rm, CC->room.QRname);
4683 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4684 CtdlGetRoom(&CC->room, hold_rm);
4689 /* We want the last (and probably only) config in this room */
4690 begin_critical_section(S_CONFIG);
4691 config_msgnum = (-1L);
4692 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4693 CtdlGetSysConfigBackend, NULL);
4694 msgnum = config_msgnum;
4695 end_critical_section(S_CONFIG);
4701 msg = CtdlFetchMessage(msgnum, 1);
4703 conf = strdup(msg->cm_fields['M']);
4704 CtdlFreeMessage(msg);
4711 CtdlGetRoom(&CC->room, hold_rm);
4713 if (conf != NULL) do {
4714 extract_token(buf, conf, 0, '\n', sizeof buf);
4715 strcpy(conf, &conf[strlen(buf)+1]);
4716 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4722 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4723 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4728 * Determine whether a given Internet address belongs to the current user
4730 int CtdlIsMe(char *addr, int addr_buf_len)
4732 struct recptypes *recp;
4735 recp = validate_recipients(addr, NULL, 0);
4736 if (recp == NULL) return(0);
4738 if (recp->num_local == 0) {
4739 free_recipients(recp);
4743 for (i=0; i<recp->num_local; ++i) {
4744 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4745 if (!strcasecmp(addr, CC->user.fullname)) {
4746 free_recipients(recp);
4751 free_recipients(recp);
4757 * Citadel protocol command to do the same
4759 void cmd_isme(char *argbuf) {
4762 if (CtdlAccessCheck(ac_logged_in)) return;
4763 extract_token(addr, argbuf, 0, '|', sizeof addr);
4765 if (CtdlIsMe(addr, sizeof addr)) {
4766 cprintf("%d %s\n", CIT_OK, addr);
4769 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4775 /*****************************************************************************/
4776 /* MODULE INITIALIZATION STUFF */
4777 /*****************************************************************************/
4779 CTDL_MODULE_INIT(msgbase)
4781 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4782 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4783 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4784 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4785 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4786 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4787 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4788 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4789 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4790 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4791 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4792 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4794 /* return our Subversion id for the Log */