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);
3119 * Convenience function for generating small administrative messages.
3121 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3122 int format_type, const char *subject)
3124 struct CtdlMessage *msg;
3125 struct recptypes *recp = NULL;
3127 msg = malloc(sizeof(struct CtdlMessage));
3128 memset(msg, 0, sizeof(struct CtdlMessage));
3129 msg->cm_magic = CTDLMESSAGE_MAGIC;
3130 msg->cm_anon_type = MES_NORMAL;
3131 msg->cm_format_type = format_type;
3134 msg->cm_fields['A'] = strdup(from);
3136 else if (fromaddr != NULL) {
3137 msg->cm_fields['A'] = strdup(fromaddr);
3138 if (strchr(msg->cm_fields['A'], '@')) {
3139 *strchr(msg->cm_fields['A'], '@') = 0;
3143 msg->cm_fields['A'] = strdup("Citadel");
3146 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3147 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3148 msg->cm_fields['N'] = strdup(NODENAME);
3150 msg->cm_fields['R'] = strdup(to);
3151 recp = validate_recipients(to, NULL, 0);
3153 if (subject != NULL) {
3154 msg->cm_fields['U'] = strdup(subject);
3156 msg->cm_fields['M'] = strdup(text);
3158 CtdlSubmitMsg(msg, recp, room, 0);
3159 CtdlFreeMessage(msg);
3160 if (recp != NULL) free_recipients(recp);
3166 * Back end function used by CtdlMakeMessage() and similar functions
3168 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3169 size_t maxlen, /* maximum message length */
3170 char *exist, /* if non-null, append to it;
3171 exist is ALWAYS freed */
3172 int crlf, /* CRLF newlines instead of LF */
3173 int sock /* socket handle or 0 for this session's client socket */
3177 size_t message_len = 0;
3178 size_t buffer_len = 0;
3185 if (exist == NULL) {
3192 message_len = strlen(exist);
3193 buffer_len = message_len + 4096;
3194 m = realloc(exist, buffer_len);
3201 /* Do we need to change leading ".." to "." for SMTP escaping? */
3202 if (!strcmp(terminator, ".")) {
3206 /* flush the input if we have nowhere to store it */
3211 /* read in the lines of message text one by one */
3214 if (sock_getln(sock, buf, (sizeof buf - 3)) < 0) finished = 1;
3217 if (client_getln(buf, (sizeof buf - 3)) < 1) finished = 1;
3219 if (!strcmp(buf, terminator)) finished = 1;
3221 strcat(buf, "\r\n");
3227 /* Unescape SMTP-style input of two dots at the beginning of the line */
3229 if (!strncmp(buf, "..", 2)) {
3230 strcpy(buf, &buf[1]);
3234 if ( (!flushing) && (!finished) ) {
3235 /* Measure the line */
3236 linelen = strlen(buf);
3238 /* augment the buffer if we have to */
3239 if ((message_len + linelen) >= buffer_len) {
3240 ptr = realloc(m, (buffer_len * 2) );
3241 if (ptr == NULL) { /* flush if can't allocate */
3244 buffer_len = (buffer_len * 2);
3246 CtdlLogPrintf(CTDL_DEBUG, "buffer_len is now %ld\n", (long)buffer_len);
3250 /* Add the new line to the buffer. NOTE: this loop must avoid
3251 * using functions like strcat() and strlen() because they
3252 * traverse the entire buffer upon every call, and doing that
3253 * for a multi-megabyte message slows it down beyond usability.
3255 strcpy(&m[message_len], buf);
3256 message_len += linelen;
3259 /* if we've hit the max msg length, flush the rest */
3260 if (message_len >= maxlen) flushing = 1;
3262 } while (!finished);
3270 * Build a binary message to be saved on disk.
3271 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3272 * will become part of the message. This means you are no longer
3273 * responsible for managing that memory -- it will be freed along with
3274 * the rest of the fields when CtdlFreeMessage() is called.)
3277 struct CtdlMessage *CtdlMakeMessage(
3278 struct ctdluser *author, /* author's user structure */
3279 char *recipient, /* NULL if it's not mail */
3280 char *recp_cc, /* NULL if it's not mail */
3281 char *room, /* room where it's going */
3282 int type, /* see MES_ types in header file */
3283 int format_type, /* variformat, plain text, MIME... */
3284 char *fake_name, /* who we're masquerading as */
3285 char *my_email, /* which of my email addresses to use (empty is ok) */
3286 char *subject, /* Subject (optional) */
3287 char *supplied_euid, /* ...or NULL if this is irrelevant */
3288 char *preformatted_text, /* ...or NULL to read text from client */
3289 char *references /* Thread references */
3291 char dest_node[256];
3293 struct CtdlMessage *msg;
3295 msg = malloc(sizeof(struct CtdlMessage));
3296 memset(msg, 0, sizeof(struct CtdlMessage));
3297 msg->cm_magic = CTDLMESSAGE_MAGIC;
3298 msg->cm_anon_type = type;
3299 msg->cm_format_type = format_type;
3301 /* Don't confuse the poor folks if it's not routed mail. */
3302 strcpy(dest_node, "");
3304 if (recipient != NULL) striplt(recipient);
3305 if (recp_cc != NULL) striplt(recp_cc);
3307 /* Path or Return-Path */
3308 if (my_email == NULL) my_email = "";
3310 if (!IsEmptyStr(my_email)) {
3311 msg->cm_fields['P'] = strdup(my_email);
3314 snprintf(buf, sizeof buf, "%s", author->fullname);
3315 msg->cm_fields['P'] = strdup(buf);
3317 convert_spaces_to_underscores(msg->cm_fields['P']);
3319 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3320 msg->cm_fields['T'] = strdup(buf);
3322 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3323 msg->cm_fields['A'] = strdup(fake_name);
3326 msg->cm_fields['A'] = strdup(author->fullname);
3329 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3330 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3333 msg->cm_fields['O'] = strdup(CC->room.QRname);
3336 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3337 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3339 if ((recipient != NULL) && (recipient[0] != 0)) {
3340 msg->cm_fields['R'] = strdup(recipient);
3342 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3343 msg->cm_fields['Y'] = strdup(recp_cc);
3345 if (dest_node[0] != 0) {
3346 msg->cm_fields['D'] = strdup(dest_node);
3349 if (!IsEmptyStr(my_email)) {
3350 msg->cm_fields['F'] = strdup(my_email);
3352 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3353 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3356 if (subject != NULL) {
3359 length = strlen(subject);
3365 while ((subject[i] != '\0') &&
3366 (IsAscii = isascii(subject[i]) != 0 ))
3369 msg->cm_fields['U'] = strdup(subject);
3370 else /* ok, we've got utf8 in the string. */
3372 msg->cm_fields['U'] = rfc2047encode(subject, length);
3378 if (supplied_euid != NULL) {
3379 msg->cm_fields['E'] = strdup(supplied_euid);
3382 if (references != NULL) {
3383 if (!IsEmptyStr(references)) {
3384 msg->cm_fields['W'] = strdup(references);
3388 if (preformatted_text != NULL) {
3389 msg->cm_fields['M'] = preformatted_text;
3392 msg->cm_fields['M'] = CtdlReadMessageBody("000", config.c_maxmsglen, NULL, 0, 0);
3400 * Check to see whether we have permission to post a message in the current
3401 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3402 * returns 0 on success.
3404 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3406 const char* RemoteIdentifier,
3410 if (!(CC->logged_in) &&
3411 (PostPublic == POST_LOGGED_IN)) {
3412 snprintf(errmsgbuf, n, "Not logged in.");
3413 return (ERROR + NOT_LOGGED_IN);
3415 else if (PostPublic == CHECK_EXISTANCE) {
3416 return (0); // We're Evaling whether a recipient exists
3418 else if (!(CC->logged_in)) {
3420 if ((CC->room.QRflags & QR_READONLY)) {
3421 snprintf(errmsgbuf, n, "Not logged in.");
3422 return (ERROR + NOT_LOGGED_IN);
3424 if (CC->room.QRflags2 & QR2_MODERATED) {
3425 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3426 return (ERROR + NOT_LOGGED_IN);
3428 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3433 if (RemoteIdentifier == NULL)
3435 snprintf(errmsgbuf, n, "Need sender to permit access.");
3436 return (ERROR + USERNAME_REQUIRED);
3439 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3440 begin_critical_section(S_NETCONFIGS);
3441 if (!read_spoolcontrol_file(&sc, filename))
3443 end_critical_section(S_NETCONFIGS);
3444 snprintf(errmsgbuf, n,
3445 "This mailing list only accepts posts from subscribers.");
3446 return (ERROR + NO_SUCH_USER);
3448 end_critical_section(S_NETCONFIGS);
3449 found = is_recipient (sc, RemoteIdentifier);
3450 free_spoolcontrol_struct(&sc);
3455 snprintf(errmsgbuf, n,
3456 "This mailing list only accepts posts from subscribers.");
3457 return (ERROR + NO_SUCH_USER);
3464 if ((CC->user.axlevel < 2)
3465 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3466 snprintf(errmsgbuf, n, "Need to be validated to enter "
3467 "(except in %s> to sysop)", MAILROOM);
3468 return (ERROR + HIGHER_ACCESS_REQUIRED);
3471 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3472 if (!(ra & UA_POSTALLOWED)) {
3473 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3474 return (ERROR + HIGHER_ACCESS_REQUIRED);
3477 strcpy(errmsgbuf, "Ok");
3483 * Check to see if the specified user has Internet mail permission
3484 * (returns nonzero if permission is granted)
3486 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3488 /* Do not allow twits to send Internet mail */
3489 if (who->axlevel <= 2) return(0);
3491 /* Globally enabled? */
3492 if (config.c_restrict == 0) return(1);
3494 /* User flagged ok? */
3495 if (who->flags & US_INTERNET) return(2);
3497 /* Aide level access? */
3498 if (who->axlevel >= 6) return(3);
3500 /* No mail for you! */
3506 * Validate recipients, count delivery types and errors, and handle aliasing
3507 * FIXME check for dupes!!!!!
3509 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3510 * were specified, or the number of addresses found invalid.
3512 * Caller needs to free the result using free_recipients()
3514 struct recptypes *validate_recipients(char *supplied_recipients,
3515 const char *RemoteIdentifier,
3517 struct recptypes *ret;
3518 char *recipients = NULL;
3519 char this_recp[256];
3520 char this_recp_cooked[256];
3526 struct ctdluser tempUS;
3527 struct ctdlroom tempQR;
3528 struct ctdlroom tempQR2;
3534 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3535 if (ret == NULL) return(NULL);
3537 /* Set all strings to null and numeric values to zero */
3538 memset(ret, 0, sizeof(struct recptypes));
3540 if (supplied_recipients == NULL) {
3541 recipients = strdup("");
3544 recipients = strdup(supplied_recipients);
3547 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3548 * actually need, but it's healthier for the heap than doing lots of tiny
3549 * realloc() calls instead.
3552 ret->errormsg = malloc(strlen(recipients) + 1024);
3553 ret->recp_local = malloc(strlen(recipients) + 1024);
3554 ret->recp_internet = malloc(strlen(recipients) + 1024);
3555 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3556 ret->recp_room = malloc(strlen(recipients) + 1024);
3557 ret->display_recp = malloc(strlen(recipients) + 1024);
3559 ret->errormsg[0] = 0;
3560 ret->recp_local[0] = 0;
3561 ret->recp_internet[0] = 0;
3562 ret->recp_ignet[0] = 0;
3563 ret->recp_room[0] = 0;
3564 ret->display_recp[0] = 0;
3566 ret->recptypes_magic = RECPTYPES_MAGIC;
3568 /* Change all valid separator characters to commas */
3569 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3570 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3571 recipients[i] = ',';
3575 /* Now start extracting recipients... */
3577 while (!IsEmptyStr(recipients)) {
3579 for (i=0; i<=strlen(recipients); ++i) {
3580 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3581 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3582 safestrncpy(this_recp, recipients, i+1);
3584 if (recipients[i] == ',') {
3585 strcpy(recipients, &recipients[i+1]);
3588 strcpy(recipients, "");
3595 if (IsEmptyStr(this_recp))
3597 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3599 mailtype = alias(this_recp);
3600 mailtype = alias(this_recp);
3601 mailtype = alias(this_recp);
3603 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3604 if (this_recp[j]=='_') {
3605 this_recp_cooked[j] = ' ';
3608 this_recp_cooked[j] = this_recp[j];
3611 this_recp_cooked[j] = '\0';
3616 if (!strcasecmp(this_recp, "sysop")) {
3618 strcpy(this_recp, config.c_aideroom);
3619 if (!IsEmptyStr(ret->recp_room)) {
3620 strcat(ret->recp_room, "|");
3622 strcat(ret->recp_room, this_recp);
3624 else if ( (!strncasecmp(this_recp, "room_", 5))
3625 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3627 /* Save room so we can restore it later */
3631 /* Check permissions to send mail to this room */
3632 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3644 if (!IsEmptyStr(ret->recp_room)) {
3645 strcat(ret->recp_room, "|");
3647 strcat(ret->recp_room, &this_recp_cooked[5]);
3650 /* Restore room in case something needs it */
3654 else if (getuser(&tempUS, this_recp) == 0) {
3656 strcpy(this_recp, tempUS.fullname);
3657 if (!IsEmptyStr(ret->recp_local)) {
3658 strcat(ret->recp_local, "|");
3660 strcat(ret->recp_local, this_recp);
3662 else if (getuser(&tempUS, this_recp_cooked) == 0) {
3664 strcpy(this_recp, tempUS.fullname);
3665 if (!IsEmptyStr(ret->recp_local)) {
3666 strcat(ret->recp_local, "|");
3668 strcat(ret->recp_local, this_recp);
3676 /* Yes, you're reading this correctly: if the target
3677 * domain points back to the local system or an attached
3678 * Citadel directory, the address is invalid. That's
3679 * because if the address were valid, we would have
3680 * already translated it to a local address by now.
3682 if (IsDirectory(this_recp, 0)) {
3687 ++ret->num_internet;
3688 if (!IsEmptyStr(ret->recp_internet)) {
3689 strcat(ret->recp_internet, "|");
3691 strcat(ret->recp_internet, this_recp);
3696 if (!IsEmptyStr(ret->recp_ignet)) {
3697 strcat(ret->recp_ignet, "|");
3699 strcat(ret->recp_ignet, this_recp);
3707 if (IsEmptyStr(errmsg)) {
3708 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3711 snprintf(append, sizeof append, "%s", errmsg);
3713 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3714 if (!IsEmptyStr(ret->errormsg)) {
3715 strcat(ret->errormsg, "; ");
3717 strcat(ret->errormsg, append);
3721 if (IsEmptyStr(ret->display_recp)) {
3722 strcpy(append, this_recp);
3725 snprintf(append, sizeof append, ", %s", this_recp);
3727 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3728 strcat(ret->display_recp, append);
3733 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3734 ret->num_room + ret->num_error) == 0) {
3735 ret->num_error = (-1);
3736 strcpy(ret->errormsg, "No recipients specified.");
3739 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3740 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3741 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3742 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3743 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3744 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3752 * Destructor for struct recptypes
3754 void free_recipients(struct recptypes *valid) {
3756 if (valid == NULL) {
3760 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3761 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3765 if (valid->errormsg != NULL) free(valid->errormsg);
3766 if (valid->recp_local != NULL) free(valid->recp_local);
3767 if (valid->recp_internet != NULL) free(valid->recp_internet);
3768 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3769 if (valid->recp_room != NULL) free(valid->recp_room);
3770 if (valid->display_recp != NULL) free(valid->display_recp);
3771 if (valid->bounce_to != NULL) free(valid->bounce_to);
3772 if (valid->envelope_from != NULL) free(valid->envelope_from);
3779 * message entry - mode 0 (normal)
3781 void cmd_ent0(char *entargs)
3787 char supplied_euid[128];
3789 int format_type = 0;
3790 char newusername[256];
3791 char newuseremail[256];
3792 struct CtdlMessage *msg;
3796 struct recptypes *valid = NULL;
3797 struct recptypes *valid_to = NULL;
3798 struct recptypes *valid_cc = NULL;
3799 struct recptypes *valid_bcc = NULL;
3801 int subject_required = 0;
3806 int newuseremail_ok = 0;
3807 char references[SIZ];
3812 post = extract_int(entargs, 0);
3813 extract_token(recp, entargs, 1, '|', sizeof recp);
3814 anon_flag = extract_int(entargs, 2);
3815 format_type = extract_int(entargs, 3);
3816 extract_token(subject, entargs, 4, '|', sizeof subject);
3817 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3818 do_confirm = extract_int(entargs, 6);
3819 extract_token(cc, entargs, 7, '|', sizeof cc);
3820 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3821 switch(CC->room.QRdefaultview) {
3824 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3827 supplied_euid[0] = 0;
3830 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3831 extract_token(references, entargs, 11, '|', sizeof references);
3832 for (ptr=references; *ptr != 0; ++ptr) {
3833 if (*ptr == '!') *ptr = '|';
3836 /* first check to make sure the request is valid. */
3838 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
3841 cprintf("%d %s\n", err, errmsg);
3845 /* Check some other permission type things. */
3847 if (IsEmptyStr(newusername)) {
3848 strcpy(newusername, CC->user.fullname);
3850 if ( (CC->user.axlevel < 6)
3851 && (strcasecmp(newusername, CC->user.fullname))
3852 && (strcasecmp(newusername, CC->cs_inet_fn))
3854 cprintf("%d You don't have permission to author messages as '%s'.\n",
3855 ERROR + HIGHER_ACCESS_REQUIRED,
3862 if (IsEmptyStr(newuseremail)) {
3863 newuseremail_ok = 1;
3866 if (!IsEmptyStr(newuseremail)) {
3867 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
3868 newuseremail_ok = 1;
3870 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
3871 j = num_tokens(CC->cs_inet_other_emails, '|');
3872 for (i=0; i<j; ++i) {
3873 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
3874 if (!strcasecmp(newuseremail, buf)) {
3875 newuseremail_ok = 1;
3881 if (!newuseremail_ok) {
3882 cprintf("%d You don't have permission to author messages as '%s'.\n",
3883 ERROR + HIGHER_ACCESS_REQUIRED,
3889 CC->cs_flags |= CS_POSTING;
3891 /* In mailbox rooms we have to behave a little differently --
3892 * make sure the user has specified at least one recipient. Then
3893 * validate the recipient(s). We do this for the Mail> room, as
3894 * well as any room which has the "Mailbox" view set.
3897 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
3898 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
3900 if (CC->user.axlevel < 2) {
3901 strcpy(recp, "sysop");
3906 valid_to = validate_recipients(recp, NULL, 0);
3907 if (valid_to->num_error > 0) {
3908 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
3909 free_recipients(valid_to);
3913 valid_cc = validate_recipients(cc, NULL, 0);
3914 if (valid_cc->num_error > 0) {
3915 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
3916 free_recipients(valid_to);
3917 free_recipients(valid_cc);
3921 valid_bcc = validate_recipients(bcc, NULL, 0);
3922 if (valid_bcc->num_error > 0) {
3923 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
3924 free_recipients(valid_to);
3925 free_recipients(valid_cc);
3926 free_recipients(valid_bcc);
3930 /* Recipient required, but none were specified */
3931 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
3932 free_recipients(valid_to);
3933 free_recipients(valid_cc);
3934 free_recipients(valid_bcc);
3935 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
3939 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
3940 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
3941 cprintf("%d You do not have permission "
3942 "to send Internet mail.\n",
3943 ERROR + HIGHER_ACCESS_REQUIRED);
3944 free_recipients(valid_to);
3945 free_recipients(valid_cc);
3946 free_recipients(valid_bcc);
3951 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)
3952 && (CC->user.axlevel < 4) ) {
3953 cprintf("%d Higher access required for network mail.\n",
3954 ERROR + HIGHER_ACCESS_REQUIRED);
3955 free_recipients(valid_to);
3956 free_recipients(valid_cc);
3957 free_recipients(valid_bcc);
3961 if ((RESTRICT_INTERNET == 1)
3962 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
3963 && ((CC->user.flags & US_INTERNET) == 0)
3964 && (!CC->internal_pgm)) {
3965 cprintf("%d You don't have access to Internet mail.\n",
3966 ERROR + HIGHER_ACCESS_REQUIRED);
3967 free_recipients(valid_to);
3968 free_recipients(valid_cc);
3969 free_recipients(valid_bcc);
3975 /* Is this a room which has anonymous-only or anonymous-option? */
3976 anonymous = MES_NORMAL;
3977 if (CC->room.QRflags & QR_ANONONLY) {
3978 anonymous = MES_ANONONLY;
3980 if (CC->room.QRflags & QR_ANONOPT) {
3981 if (anon_flag == 1) { /* only if the user requested it */
3982 anonymous = MES_ANONOPT;
3986 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
3990 /* Recommend to the client that the use of a message subject is
3991 * strongly recommended in this room, if either the SUBJECTREQ flag
3992 * is set, or if there is one or more Internet email recipients.
3994 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
3995 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
3996 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
3997 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
3999 /* If we're only checking the validity of the request, return
4000 * success without creating the message.
4003 cprintf("%d %s|%d\n", CIT_OK,
4004 ((valid_to != NULL) ? valid_to->display_recp : ""),
4006 free_recipients(valid_to);
4007 free_recipients(valid_cc);
4008 free_recipients(valid_bcc);
4012 /* We don't need these anymore because we'll do it differently below */
4013 free_recipients(valid_to);
4014 free_recipients(valid_cc);
4015 free_recipients(valid_bcc);
4017 /* Read in the message from the client. */
4019 cprintf("%d send message\n", START_CHAT_MODE);
4021 cprintf("%d send message\n", SEND_LISTING);
4024 msg = CtdlMakeMessage(&CC->user, recp, cc,
4025 CC->room.QRname, anonymous, format_type,
4026 newusername, newuseremail, subject,
4027 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4030 /* Put together one big recipients struct containing to/cc/bcc all in
4031 * one. This is for the envelope.
4033 char *all_recps = malloc(SIZ * 3);
4034 strcpy(all_recps, recp);
4035 if (!IsEmptyStr(cc)) {
4036 if (!IsEmptyStr(all_recps)) {
4037 strcat(all_recps, ",");
4039 strcat(all_recps, cc);
4041 if (!IsEmptyStr(bcc)) {
4042 if (!IsEmptyStr(all_recps)) {
4043 strcat(all_recps, ",");
4045 strcat(all_recps, bcc);
4047 if (!IsEmptyStr(all_recps)) {
4048 valid = validate_recipients(all_recps, NULL, 0);
4056 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4059 cprintf("%ld\n", msgnum);
4061 cprintf("Message accepted.\n");
4064 cprintf("Internal error.\n");
4066 if (msg->cm_fields['E'] != NULL) {
4067 cprintf("%s\n", msg->cm_fields['E']);
4074 CtdlFreeMessage(msg);
4076 if (valid != NULL) {
4077 free_recipients(valid);
4085 * API function to delete messages which match a set of criteria
4086 * (returns the actual number of messages deleted)
4088 int CtdlDeleteMessages(char *room_name, /* which room */
4089 long *dmsgnums, /* array of msg numbers to be deleted */
4090 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4091 char *content_type /* or "" for any. regular expressions expected. */
4094 struct ctdlroom qrbuf;
4095 struct cdbdata *cdbfr;
4096 long *msglist = NULL;
4097 long *dellist = NULL;
4100 int num_deleted = 0;
4102 struct MetaData smi;
4105 int need_to_free_re = 0;
4107 if (content_type) if (!IsEmptyStr(content_type)) {
4108 regcomp(&re, content_type, 0);
4109 need_to_free_re = 1;
4111 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4112 room_name, num_dmsgnums, content_type);
4114 /* get room record, obtaining a lock... */
4115 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4116 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4118 if (need_to_free_re) regfree(&re);
4119 return (0); /* room not found */
4121 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4123 if (cdbfr != NULL) {
4124 dellist = malloc(cdbfr->len);
4125 msglist = (long *) cdbfr->ptr;
4126 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4127 num_msgs = cdbfr->len / sizeof(long);
4131 for (i = 0; i < num_msgs; ++i) {
4134 /* Set/clear a bit for each criterion */
4136 /* 0 messages in the list or a null list means that we are
4137 * interested in deleting any messages which meet the other criteria.
4139 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4140 delete_this |= 0x01;
4143 for (j=0; j<num_dmsgnums; ++j) {
4144 if (msglist[i] == dmsgnums[j]) {
4145 delete_this |= 0x01;
4150 if (IsEmptyStr(content_type)) {
4151 delete_this |= 0x02;
4153 GetMetaData(&smi, msglist[i]);
4154 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4155 delete_this |= 0x02;
4159 /* Delete message only if all bits are set */
4160 if (delete_this == 0x03) {
4161 dellist[num_deleted++] = msglist[i];
4166 num_msgs = sort_msglist(msglist, num_msgs);
4167 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4168 msglist, (int)(num_msgs * sizeof(long)));
4170 qrbuf.QRhighest = msglist[num_msgs - 1];
4172 CtdlPutRoomLock(&qrbuf);
4174 /* Go through the messages we pulled out of the index, and decrement
4175 * their reference counts by 1. If this is the only room the message
4176 * was in, the reference count will reach zero and the message will
4177 * automatically be deleted from the database. We do this in a
4178 * separate pass because there might be plug-in hooks getting called,
4179 * and we don't want that happening during an S_ROOMS critical
4182 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4183 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4184 AdjRefCount(dellist[i], -1);
4187 /* Now free the memory we used, and go away. */
4188 if (msglist != NULL) free(msglist);
4189 if (dellist != NULL) free(dellist);
4190 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4191 if (need_to_free_re) regfree(&re);
4192 return (num_deleted);
4198 * Check whether the current user has permission to delete messages from
4199 * the current room (returns 1 for yes, 0 for no)
4201 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4203 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4204 if (ra & UA_DELETEALLOWED) return(1);
4212 * Delete message from current room
4214 void cmd_dele(char *args)
4223 extract_token(msgset, args, 0, '|', sizeof msgset);
4224 num_msgs = num_tokens(msgset, ',');
4226 cprintf("%d Nothing to do.\n", CIT_OK);
4230 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4231 cprintf("%d Higher access required.\n",
4232 ERROR + HIGHER_ACCESS_REQUIRED);
4237 * Build our message set to be moved/copied
4239 msgs = malloc(num_msgs * sizeof(long));
4240 for (i=0; i<num_msgs; ++i) {
4241 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4242 msgs[i] = atol(msgtok);
4245 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4249 cprintf("%d %d message%s deleted.\n", CIT_OK,
4250 num_deleted, ((num_deleted != 1) ? "s" : ""));
4252 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4260 * move or copy a message to another room
4262 void cmd_move(char *args)
4269 char targ[ROOMNAMELEN];
4270 struct ctdlroom qtemp;
4277 extract_token(msgset, args, 0, '|', sizeof msgset);
4278 num_msgs = num_tokens(msgset, ',');
4280 cprintf("%d Nothing to do.\n", CIT_OK);
4284 extract_token(targ, args, 1, '|', sizeof targ);
4285 convert_room_name_macros(targ, sizeof targ);
4286 targ[ROOMNAMELEN - 1] = 0;
4287 is_copy = extract_int(args, 2);
4289 if (CtdlGetRoom(&qtemp, targ) != 0) {
4290 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4294 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4295 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4299 getuser(&CC->user, CC->curr_user);
4300 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4302 /* Check for permission to perform this operation.
4303 * Remember: "CC->room" is source, "qtemp" is target.
4307 /* Aides can move/copy */
4308 if (CC->user.axlevel >= 6) permit = 1;
4310 /* Room aides can move/copy */
4311 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4313 /* Permit move/copy from personal rooms */
4314 if ((CC->room.QRflags & QR_MAILBOX)
4315 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4317 /* Permit only copy from public to personal room */
4319 && (!(CC->room.QRflags & QR_MAILBOX))
4320 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4322 /* Permit message removal from collaborative delete rooms */
4323 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4325 /* Users allowed to post into the target room may move into it too. */
4326 if ((CC->room.QRflags & QR_MAILBOX) &&
4327 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4329 /* User must have access to target room */
4330 if (!(ra & UA_KNOWN)) permit = 0;
4333 cprintf("%d Higher access required.\n",
4334 ERROR + HIGHER_ACCESS_REQUIRED);
4339 * Build our message set to be moved/copied
4341 msgs = malloc(num_msgs * sizeof(long));
4342 for (i=0; i<num_msgs; ++i) {
4343 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4344 msgs[i] = atol(msgtok);
4350 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL);
4352 cprintf("%d Cannot store message(s) in %s: error %d\n",
4358 /* Now delete the message from the source room,
4359 * if this is a 'move' rather than a 'copy' operation.
4362 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4366 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4372 * GetMetaData() - Get the supplementary record for a message
4374 void GetMetaData(struct MetaData *smibuf, long msgnum)
4377 struct cdbdata *cdbsmi;
4380 memset(smibuf, 0, sizeof(struct MetaData));
4381 smibuf->meta_msgnum = msgnum;
4382 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4384 /* Use the negative of the message number for its supp record index */
4385 TheIndex = (0L - msgnum);
4387 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4388 if (cdbsmi == NULL) {
4389 return; /* record not found; go with defaults */
4391 memcpy(smibuf, cdbsmi->ptr,
4392 ((cdbsmi->len > sizeof(struct MetaData)) ?
4393 sizeof(struct MetaData) : cdbsmi->len));
4400 * PutMetaData() - (re)write supplementary record for a message
4402 void PutMetaData(struct MetaData *smibuf)
4406 /* Use the negative of the message number for the metadata db index */
4407 TheIndex = (0L - smibuf->meta_msgnum);
4409 cdb_store(CDB_MSGMAIN,
4410 &TheIndex, (int)sizeof(long),
4411 smibuf, (int)sizeof(struct MetaData));
4416 * AdjRefCount - submit an adjustment to the reference count for a message.
4417 * (These are just queued -- we actually process them later.)
4419 void AdjRefCount(long msgnum, int incr)
4421 struct arcq new_arcq;
4424 begin_critical_section(S_SUPPMSGMAIN);
4425 if (arcfp == NULL) {
4426 arcfp = fopen(file_arcq, "ab+");
4428 end_critical_section(S_SUPPMSGMAIN);
4430 /* msgnum < 0 means that we're trying to close the file */
4432 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4433 begin_critical_section(S_SUPPMSGMAIN);
4434 if (arcfp != NULL) {
4438 end_critical_section(S_SUPPMSGMAIN);
4443 * If we can't open the queue, perform the operation synchronously.
4445 if (arcfp == NULL) {
4446 TDAP_AdjRefCount(msgnum, incr);
4450 new_arcq.arcq_msgnum = msgnum;
4451 new_arcq.arcq_delta = incr;
4452 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4460 * TDAP_ProcessAdjRefCountQueue()
4462 * Process the queue of message count adjustments that was created by calls
4463 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4464 * for each one. This should be an "off hours" operation.
4466 int TDAP_ProcessAdjRefCountQueue(void)
4468 char file_arcq_temp[PATH_MAX];
4471 struct arcq arcq_rec;
4472 int num_records_processed = 0;
4474 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4476 begin_critical_section(S_SUPPMSGMAIN);
4477 if (arcfp != NULL) {
4482 r = link(file_arcq, file_arcq_temp);
4484 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4485 end_critical_section(S_SUPPMSGMAIN);
4486 return(num_records_processed);
4490 end_critical_section(S_SUPPMSGMAIN);
4492 fp = fopen(file_arcq_temp, "rb");
4494 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4495 return(num_records_processed);
4498 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4499 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4500 ++num_records_processed;
4504 r = unlink(file_arcq_temp);
4506 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4509 return(num_records_processed);
4515 * TDAP_AdjRefCount - adjust the reference count for a message.
4516 * This one does it "for real" because it's called by
4517 * the autopurger function that processes the queue
4518 * created by AdjRefCount(). If a message's reference
4519 * count becomes zero, we also delete the message from
4520 * disk and de-index it.
4522 void TDAP_AdjRefCount(long msgnum, int incr)
4525 struct MetaData smi;
4528 /* This is a *tight* critical section; please keep it that way, as
4529 * it may get called while nested in other critical sections.
4530 * Complicating this any further will surely cause deadlock!
4532 begin_critical_section(S_SUPPMSGMAIN);
4533 GetMetaData(&smi, msgnum);
4534 smi.meta_refcount += incr;
4536 end_critical_section(S_SUPPMSGMAIN);
4537 CtdlLogPrintf(CTDL_DEBUG, "msg %ld ref count delta %+d, is now %d\n",
4538 msgnum, incr, smi.meta_refcount);
4540 /* If the reference count is now zero, delete the message
4541 * (and its supplementary record as well).
4543 if (smi.meta_refcount == 0) {
4544 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4546 /* Call delete hooks with NULL room to show it has gone altogether */
4547 PerformDeleteHooks(NULL, msgnum);
4549 /* Remove from message base */
4551 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4552 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4554 /* Remove metadata record */
4555 delnum = (0L - msgnum);
4556 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4562 * Write a generic object to this room
4564 * Note: this could be much more efficient. Right now we use two temporary
4565 * files, and still pull the message into memory as with all others.
4567 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4568 char *content_type, /* MIME type of this object */
4569 char *raw_message, /* Data to be written */
4570 off_t raw_length, /* Size of raw_message */
4571 struct ctdluser *is_mailbox, /* Mailbox room? */
4572 int is_binary, /* Is encoding necessary? */
4573 int is_unique, /* Del others of this type? */
4574 unsigned int flags /* Internal save flags */
4578 struct ctdlroom qrbuf;
4579 char roomname[ROOMNAMELEN];
4580 struct CtdlMessage *msg;
4581 char *encoded_message = NULL;
4583 if (is_mailbox != NULL) {
4584 MailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4587 safestrncpy(roomname, req_room, sizeof(roomname));
4590 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4593 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4596 encoded_message = malloc((size_t)(raw_length + 4096));
4599 sprintf(encoded_message, "Content-type: %s\n", content_type);
4602 sprintf(&encoded_message[strlen(encoded_message)],
4603 "Content-transfer-encoding: base64\n\n"
4607 sprintf(&encoded_message[strlen(encoded_message)],
4608 "Content-transfer-encoding: 7bit\n\n"
4614 &encoded_message[strlen(encoded_message)],
4622 &encoded_message[strlen(encoded_message)],
4628 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4629 msg = malloc(sizeof(struct CtdlMessage));
4630 memset(msg, 0, sizeof(struct CtdlMessage));
4631 msg->cm_magic = CTDLMESSAGE_MAGIC;
4632 msg->cm_anon_type = MES_NORMAL;
4633 msg->cm_format_type = 4;
4634 msg->cm_fields['A'] = strdup(CC->user.fullname);
4635 msg->cm_fields['O'] = strdup(req_room);
4636 msg->cm_fields['N'] = strdup(config.c_nodename);
4637 msg->cm_fields['H'] = strdup(config.c_humannode);
4638 msg->cm_flags = flags;
4640 msg->cm_fields['M'] = encoded_message;
4642 /* Create the requested room if we have to. */
4643 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4644 CtdlCreateRoom(roomname,
4645 ( (is_mailbox != NULL) ? 5 : 3 ),
4646 "", 0, 1, 0, VIEW_BBS);
4648 /* If the caller specified this object as unique, delete all
4649 * other objects of this type that are currently in the room.
4652 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4653 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4656 /* Now write the data */
4657 CtdlSubmitMsg(msg, NULL, roomname, 0);
4658 CtdlFreeMessage(msg);
4666 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4667 config_msgnum = msgnum;
4671 char *CtdlGetSysConfig(char *sysconfname) {
4672 char hold_rm[ROOMNAMELEN];
4675 struct CtdlMessage *msg;
4678 strcpy(hold_rm, CC->room.QRname);
4679 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4680 CtdlGetRoom(&CC->room, hold_rm);
4685 /* We want the last (and probably only) config in this room */
4686 begin_critical_section(S_CONFIG);
4687 config_msgnum = (-1L);
4688 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4689 CtdlGetSysConfigBackend, NULL);
4690 msgnum = config_msgnum;
4691 end_critical_section(S_CONFIG);
4697 msg = CtdlFetchMessage(msgnum, 1);
4699 conf = strdup(msg->cm_fields['M']);
4700 CtdlFreeMessage(msg);
4707 CtdlGetRoom(&CC->room, hold_rm);
4709 if (conf != NULL) do {
4710 extract_token(buf, conf, 0, '\n', sizeof buf);
4711 strcpy(conf, &conf[strlen(buf)+1]);
4712 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4718 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4719 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4724 * Determine whether a given Internet address belongs to the current user
4726 int CtdlIsMe(char *addr, int addr_buf_len)
4728 struct recptypes *recp;
4731 recp = validate_recipients(addr, NULL, 0);
4732 if (recp == NULL) return(0);
4734 if (recp->num_local == 0) {
4735 free_recipients(recp);
4739 for (i=0; i<recp->num_local; ++i) {
4740 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4741 if (!strcasecmp(addr, CC->user.fullname)) {
4742 free_recipients(recp);
4747 free_recipients(recp);
4753 * Citadel protocol command to do the same
4755 void cmd_isme(char *argbuf) {
4758 if (CtdlAccessCheck(ac_logged_in)) return;
4759 extract_token(addr, argbuf, 0, '|', sizeof addr);
4761 if (CtdlIsMe(addr, sizeof addr)) {
4762 cprintf("%d %s\n", CIT_OK, addr);
4765 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4771 /*****************************************************************************/
4772 /* MODULE INITIALIZATION STUFF */
4773 /*****************************************************************************/
4775 CTDL_MODULE_INIT(msgbase)
4777 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4778 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4779 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4780 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4781 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4782 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4783 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4784 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4785 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4786 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4787 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4788 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4790 /* return our Subversion id for the Log */