2 * Implements the message store.
4 * Copyright (c) 1987-2010 by the citadel.org team
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
27 #if TIME_WITH_SYS_TIME
28 # include <sys/time.h>
32 # include <sys/time.h>
45 #include <sys/types.h>
47 #include <libcitadel.h>
50 #include "serv_extensions.h"
54 #include "sysdep_decls.h"
55 #include "citserver.h"
62 #include "internet_addressing.h"
63 #include "euidindex.h"
64 #include "journaling.h"
65 #include "citadel_dirs.h"
66 #include "clientsocket.h"
67 #include "serv_network.h"
70 #include "ctdl_module.h"
73 struct addresses_to_be_filed *atbf = NULL;
75 /* This temp file holds the queue of operations for AdjRefCount() */
76 static FILE *arcfp = NULL;
79 * This really belongs in serv_network.c, but I don't know how to export
80 * symbols between modules.
82 struct FilterList *filterlist = NULL;
86 * These are the four-character field headers we use when outputting
87 * messages in Citadel format (as opposed to RFC822 format).
90 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
91 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
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,
126 * This function is self explanatory.
127 * (What can I say, I'm in a weird mood today...)
129 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
133 for (i = 0; i < strlen(name); ++i) {
134 if (name[i] == '@') {
135 while (isspace(name[i - 1]) && i > 0) {
136 strcpy(&name[i - 1], &name[i]);
139 while (isspace(name[i + 1])) {
140 strcpy(&name[i + 1], &name[i + 2]);
148 * Aliasing for network mail.
149 * (Error messages have been commented out, because this is a server.)
151 int alias(char *name)
152 { /* process alias and routing info for mail */
155 char aaa[SIZ], bbb[SIZ];
156 char *ignetcfg = NULL;
157 char *ignetmap = NULL;
163 char original_name[256];
164 safestrncpy(original_name, name, sizeof original_name);
167 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
168 stripallbut(name, '<', '>');
170 fp = fopen(file_mail_aliases, "r");
172 fp = fopen("/dev/null", "r");
179 while (fgets(aaa, sizeof aaa, fp) != NULL) {
180 while (isspace(name[0]))
181 strcpy(name, &name[1]);
182 aaa[strlen(aaa) - 1] = 0;
184 for (a = 0; a < strlen(aaa); ++a) {
186 strcpy(bbb, &aaa[a + 1]);
190 if (!strcasecmp(name, aaa))
195 /* Hit the Global Address Book */
196 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
200 if (strcasecmp(original_name, name)) {
201 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
204 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
205 for (a=0; a<strlen(name); ++a) {
206 if (name[a] == '@') {
207 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
209 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
214 /* determine local or remote type, see citadel.h */
215 at = haschar(name, '@');
216 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
217 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
218 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
220 /* figure out the delivery mode */
221 extract_token(node, name, 1, '@', sizeof node);
223 /* If there are one or more dots in the nodename, we assume that it
224 * is an FQDN and will attempt SMTP delivery to the Internet.
226 if (haschar(node, '.') > 0) {
227 return(MES_INTERNET);
230 /* Otherwise we look in the IGnet maps for a valid Citadel node.
231 * Try directly-connected nodes first...
233 ignetcfg = CtdlGetSysConfig(IGNETCFG);
234 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
235 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
236 extract_token(testnode, buf, 0, '|', sizeof testnode);
237 if (!strcasecmp(node, testnode)) {
245 * Then try nodes that are two or more hops away.
247 ignetmap = CtdlGetSysConfig(IGNETMAP);
248 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
249 extract_token(buf, ignetmap, i, '\n', sizeof buf);
250 extract_token(testnode, buf, 0, '|', sizeof testnode);
251 if (!strcasecmp(node, testnode)) {
258 /* If we get to this point it's an invalid node name */
264 * Back end for the MSGS command: output message number only.
266 void simple_listing(long msgnum, void *userdata)
268 cprintf("%ld\n", msgnum);
274 * Back end for the MSGS command: output header summary.
276 void headers_listing(long msgnum, void *userdata)
278 struct CtdlMessage *msg;
280 msg = CtdlFetchMessage(msgnum, 0);
282 cprintf("%ld|0|||||\n", msgnum);
286 cprintf("%ld|%s|%s|%s|%s|%s|\n",
288 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
289 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
290 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
291 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
292 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
294 CtdlFreeMessage(msg);
298 * Back end for the MSGS command: output EUID header.
300 void headers_euid(long msgnum, void *userdata)
302 struct CtdlMessage *msg;
304 msg = CtdlFetchMessage(msgnum, 0);
306 cprintf("%ld||\n", msgnum);
310 cprintf("%ld|%s|%s\n",
312 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
313 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
314 CtdlFreeMessage(msg);
321 /* Determine if a given message matches the fields in a message template.
322 * Return 0 for a successful match.
324 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
327 /* If there aren't any fields in the template, all messages will
330 if (template == NULL) return(0);
332 /* Null messages are bogus. */
333 if (msg == NULL) return(1);
335 for (i='A'; i<='Z'; ++i) {
336 if (template->cm_fields[i] != NULL) {
337 if (msg->cm_fields[i] == NULL) {
338 /* Considered equal if temmplate is empty string */
339 if (IsEmptyStr(template->cm_fields[i])) continue;
342 if (strcasecmp(msg->cm_fields[i],
343 template->cm_fields[i])) return 1;
347 /* All compares succeeded: we have a match! */
354 * Retrieve the "seen" message list for the current room.
356 void CtdlGetSeen(char *buf, int which_set) {
359 /* Learn about the user and room in question */
360 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
362 if (which_set == ctdlsetseen_seen)
363 safestrncpy(buf, vbuf.v_seen, SIZ);
364 if (which_set == ctdlsetseen_answered)
365 safestrncpy(buf, vbuf.v_answered, SIZ);
371 * Manipulate the "seen msgs" string (or other message set strings)
373 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
374 int target_setting, int which_set,
375 struct ctdluser *which_user, struct ctdlroom *which_room) {
376 struct cdbdata *cdbfr;
390 char *is_set; /* actually an array of booleans */
392 /* Don't bother doing *anything* if we were passed a list of zero messages */
393 if (num_target_msgnums < 1) {
397 /* If no room was specified, we go with the current room. */
399 which_room = &CC->room;
402 /* If no user was specified, we go with the current user. */
404 which_user = &CC->user;
407 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
408 num_target_msgnums, target_msgnums[0],
409 (target_setting ? "SET" : "CLEAR"),
413 /* Learn about the user and room in question */
414 CtdlGetRelationship(&vbuf, which_user, which_room);
416 /* Load the message list */
417 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
419 msglist = (long *) cdbfr->ptr;
420 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
421 num_msgs = cdbfr->len / sizeof(long);
424 return; /* No messages at all? No further action. */
427 is_set = malloc(num_msgs * sizeof(char));
428 memset(is_set, 0, (num_msgs * sizeof(char)) );
430 /* Decide which message set we're manipulating */
432 case ctdlsetseen_seen:
433 vset = NewStrBufPlain(vbuf.v_seen, -1);
435 case ctdlsetseen_answered:
436 vset = NewStrBufPlain(vbuf.v_answered, -1);
443 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
444 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
445 for (i=0; i<num_msgs; ++i) {
446 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
448 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
449 for (k=0; k<num_target_msgnums; ++k) {
450 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
454 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
456 /* Translate the existing sequence set into an array of booleans */
457 setstr = NewStrBuf();
461 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
463 StrBufExtract_token(lostr, setstr, 0, ':');
464 if (StrBufNum_tokens(setstr, ':') >= 2) {
465 StrBufExtract_token(histr, setstr, 1, ':');
469 StrBufAppendBuf(histr, lostr, 0);
472 if (!strcmp(ChrPtr(histr), "*")) {
479 for (i = 0; i < num_msgs; ++i) {
480 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
490 /* Now translate the array of booleans back into a sequence set */
496 for (i=0; i<num_msgs; ++i) {
500 for (k=0; k<num_target_msgnums; ++k) {
501 if (msglist[i] == target_msgnums[k]) {
502 is_seen = target_setting;
506 if ((was_seen == 0) && (is_seen == 1)) {
509 else if ((was_seen == 1) && (is_seen == 0)) {
512 if (StrLength(vset) > 0) {
513 StrBufAppendBufPlain(vset, HKEY(","), 0);
516 StrBufAppendPrintf(vset, "%ld", hi);
519 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
523 if ((is_seen) && (i == num_msgs - 1)) {
524 if (StrLength(vset) > 0) {
525 StrBufAppendBufPlain(vset, HKEY(","), 0);
527 if ((i==0) || (was_seen == 0)) {
528 StrBufAppendPrintf(vset, "%ld", msglist[i]);
531 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
539 * We will have to stuff this string back into a 4096 byte buffer, so if it's
540 * larger than that now, truncate it by removing tokens from the beginning.
541 * The limit of 100 iterations is there to prevent an infinite loop in case
542 * something unexpected happens.
544 int number_of_truncations = 0;
545 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
546 StrBufRemove_token(vset, 0, ',');
547 ++number_of_truncations;
551 * If we're truncating the sequence set of messages marked with the 'seen' flag,
552 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
553 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
555 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
557 first_tok = NewStrBuf();
558 StrBufExtract_token(first_tok, vset, 0, ',');
559 StrBufRemove_token(vset, 0, ',');
561 if (StrBufNum_tokens(first_tok, ':') > 1) {
562 StrBufRemove_token(first_tok, 0, ':');
566 new_set = NewStrBuf();
567 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
568 StrBufAppendBuf(new_set, first_tok, 0);
569 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
570 StrBufAppendBuf(new_set, vset, 0);
573 FreeStrBuf(&first_tok);
577 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
579 /* Decide which message set we're manipulating */
581 case ctdlsetseen_seen:
582 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
584 case ctdlsetseen_answered:
585 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
591 CtdlSetRelationship(&vbuf, which_user, which_room);
597 * API function to perform an operation for each qualifying message in the
598 * current room. (Returns the number of messages processed.)
600 int CtdlForEachMessage(int mode, long ref, char *search_string,
602 struct CtdlMessage *compare,
603 ForEachMsgCallback CallBack,
609 struct cdbdata *cdbfr;
610 long *msglist = NULL;
612 int num_processed = 0;
615 struct CtdlMessage *msg = NULL;
618 int printed_lastold = 0;
619 int num_search_msgs = 0;
620 long *search_msgs = NULL;
622 int need_to_free_re = 0;
625 if ((content_type) && (!IsEmptyStr(content_type))) {
626 regcomp(&re, content_type, 0);
630 /* Learn about the user and room in question */
631 CtdlGetUser(&CC->user, CC->curr_user);
632 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
634 /* Load the message list */
635 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
637 msglist = (long *) cdbfr->ptr;
638 num_msgs = cdbfr->len / sizeof(long);
640 if (need_to_free_re) regfree(&re);
641 return 0; /* No messages at all? No further action. */
646 * Now begin the traversal.
648 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
650 /* If the caller is looking for a specific MIME type, filter
651 * out all messages which are not of the type requested.
653 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
655 /* This call to GetMetaData() sits inside this loop
656 * so that we only do the extra database read per msg
657 * if we need to. Doing the extra read all the time
658 * really kills the server. If we ever need to use
659 * metadata for another search criterion, we need to
660 * move the read somewhere else -- but still be smart
661 * enough to only do the read if the caller has
662 * specified something that will need it.
664 GetMetaData(&smi, msglist[a]);
666 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
667 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
673 num_msgs = sort_msglist(msglist, num_msgs);
675 /* If a template was supplied, filter out the messages which
676 * don't match. (This could induce some delays!)
679 if (compare != NULL) {
680 for (a = 0; a < num_msgs; ++a) {
681 msg = CtdlFetchMessage(msglist[a], 1);
683 if (CtdlMsgCmp(msg, compare)) {
686 CtdlFreeMessage(msg);
692 /* If a search string was specified, get a message list from
693 * the full text index and remove messages which aren't on both
697 * Since the lists are sorted and strictly ascending, and the
698 * output list is guaranteed to be shorter than or equal to the
699 * input list, we overwrite the bottom of the input list. This
700 * eliminates the need to memmove big chunks of the list over and
703 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
705 /* Call search module via hook mechanism.
706 * NULL means use any search function available.
707 * otherwise replace with a char * to name of search routine
709 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
711 if (num_search_msgs > 0) {
715 orig_num_msgs = num_msgs;
717 for (i=0; i<orig_num_msgs; ++i) {
718 for (j=0; j<num_search_msgs; ++j) {
719 if (msglist[i] == search_msgs[j]) {
720 msglist[num_msgs++] = msglist[i];
726 num_msgs = 0; /* No messages qualify */
728 if (search_msgs != NULL) free(search_msgs);
730 /* Now that we've purged messages which don't contain the search
731 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
738 * Now iterate through the message list, according to the
739 * criteria supplied by the caller.
742 for (a = 0; a < num_msgs; ++a) {
743 thismsg = msglist[a];
744 if (mode == MSGS_ALL) {
748 is_seen = is_msg_in_sequence_set(
749 vbuf.v_seen, thismsg);
750 if (is_seen) lastold = thismsg;
756 || ((mode == MSGS_OLD) && (is_seen))
757 || ((mode == MSGS_NEW) && (!is_seen))
758 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
759 || ((mode == MSGS_FIRST) && (a < ref))
760 || ((mode == MSGS_GT) && (thismsg > ref))
761 || ((mode == MSGS_LT) && (thismsg < ref))
762 || ((mode == MSGS_EQ) && (thismsg == ref))
765 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
767 CallBack(lastold, userdata);
771 if (CallBack) CallBack(thismsg, userdata);
775 cdb_free(cdbfr); /* Clean up */
776 if (need_to_free_re) regfree(&re);
777 return num_processed;
783 * cmd_msgs() - get list of message #'s in this room
784 * implements the MSGS server command using CtdlForEachMessage()
786 void cmd_msgs(char *cmdbuf)
795 int with_template = 0;
796 struct CtdlMessage *template = NULL;
797 char search_string[1024];
798 ForEachMsgCallback CallBack;
800 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
802 extract_token(which, cmdbuf, 0, '|', sizeof which);
803 cm_ref = extract_int(cmdbuf, 1);
804 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
805 with_template = extract_int(cmdbuf, 2);
806 switch (extract_int(cmdbuf, 3))
810 CallBack = simple_listing;
813 CallBack = headers_listing;
816 CallBack = headers_euid;
821 if (!strncasecmp(which, "OLD", 3))
823 else if (!strncasecmp(which, "NEW", 3))
825 else if (!strncasecmp(which, "FIRST", 5))
827 else if (!strncasecmp(which, "LAST", 4))
829 else if (!strncasecmp(which, "GT", 2))
831 else if (!strncasecmp(which, "LT", 2))
833 else if (!strncasecmp(which, "SEARCH", 6))
838 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
839 cprintf("%d Full text index is not enabled on this server.\n",
840 ERROR + CMD_NOT_SUPPORTED);
846 cprintf("%d Send template then receive message list\n",
848 template = (struct CtdlMessage *)
849 malloc(sizeof(struct CtdlMessage));
850 memset(template, 0, sizeof(struct CtdlMessage));
851 template->cm_magic = CTDLMESSAGE_MAGIC;
852 template->cm_anon_type = MES_NORMAL;
854 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
855 extract_token(tfield, buf, 0, '|', sizeof tfield);
856 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
857 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
858 if (!strcasecmp(tfield, msgkeys[i])) {
859 template->cm_fields[i] =
867 cprintf("%d \n", LISTING_FOLLOWS);
870 CtdlForEachMessage(mode,
871 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
872 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
877 if (template != NULL) CtdlFreeMessage(template);
885 * help_subst() - support routine for help file viewer
887 void help_subst(char *strbuf, char *source, char *dest)
892 while (p = pattern2(strbuf, source), (p >= 0)) {
893 strcpy(workbuf, &strbuf[p + strlen(source)]);
894 strcpy(&strbuf[p], dest);
895 strcat(strbuf, workbuf);
900 void do_help_subst(char *buffer)
904 help_subst(buffer, "^nodename", config.c_nodename);
905 help_subst(buffer, "^humannode", config.c_humannode);
906 help_subst(buffer, "^fqdn", config.c_fqdn);
907 help_subst(buffer, "^username", CC->user.fullname);
908 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
909 help_subst(buffer, "^usernum", buf2);
910 help_subst(buffer, "^sysadm", config.c_sysadm);
911 help_subst(buffer, "^variantname", CITADEL);
912 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
913 help_subst(buffer, "^maxsessions", buf2);
914 help_subst(buffer, "^bbsdir", ctdl_message_dir);
920 * memfmout() - Citadel text formatter and paginator.
921 * Although the original purpose of this routine was to format
922 * text to the reader's screen width, all we're really using it
923 * for here is to format text out to 80 columns before sending it
924 * to the client. The client software may reformat it again.
927 char *mptr, /* where are we going to get our text from? */
928 const char *nl /* string to terminate lines with */
931 unsigned char ch = 0;
938 while (ch=*(mptr++), ch != 0) {
941 if (client_write(outbuf, len) == -1)
943 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
947 if (client_write(nl, nllen) == -1)
949 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
954 else if (ch == '\r') {
955 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
957 else if (isspace(ch)) {
958 if (column > 72) { /* Beyond 72 columns, break on the next space */
959 if (client_write(outbuf, len) == -1)
961 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
965 if (client_write(nl, nllen) == -1)
967 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
980 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
981 if (client_write(outbuf, len) == -1)
983 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
987 if (client_write(nl, nllen) == -1)
989 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
997 if (client_write(outbuf, len) == -1)
999 CtdlLogPrintf(CTDL_ERR, "memfmout(): aborting due to write failure.\n");
1003 client_write(nl, nllen);
1011 * Callback function for mime parser that simply lists the part
1013 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1014 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1015 char *cbid, void *cbuserdata)
1019 ma = (struct ma_info *)cbuserdata;
1020 if (ma->is_ma == 0) {
1021 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1034 * Callback function for multipart prefix
1036 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1037 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1038 char *cbid, void *cbuserdata)
1042 ma = (struct ma_info *)cbuserdata;
1043 if (!strcasecmp(cbtype, "multipart/alternative")) {
1047 if (ma->is_ma == 0) {
1048 cprintf("pref=%s|%s\n", partnum, cbtype);
1053 * Callback function for multipart sufffix
1055 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1056 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1057 char *cbid, void *cbuserdata)
1061 ma = (struct ma_info *)cbuserdata;
1062 if (ma->is_ma == 0) {
1063 cprintf("suff=%s|%s\n", partnum, cbtype);
1065 if (!strcasecmp(cbtype, "multipart/alternative")) {
1072 * Callback function for mime parser that opens a section for downloading
1074 void mime_download(char *name, char *filename, char *partnum, char *disp,
1075 void *content, char *cbtype, char *cbcharset, size_t length,
1076 char *encoding, char *cbid, void *cbuserdata)
1080 /* Silently go away if there's already a download open. */
1081 if (CC->download_fp != NULL)
1085 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1086 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1088 CC->download_fp = tmpfile();
1089 if (CC->download_fp == NULL)
1092 rv = fwrite(content, length, 1, CC->download_fp);
1093 fflush(CC->download_fp);
1094 rewind(CC->download_fp);
1096 OpenCmdResult(filename, cbtype);
1103 * Callback function for mime parser that outputs a section all at once.
1104 * We can specify the desired section by part number *or* content-id.
1106 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1107 void *content, char *cbtype, char *cbcharset, size_t length,
1108 char *encoding, char *cbid, void *cbuserdata)
1110 int *found_it = (int *)cbuserdata;
1113 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1114 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1117 cprintf("%d %d|-1|%s|%s|%s\n",
1124 client_write(content, length);
1128 #ifdef MESSAGE_IN_ROOM
1130 * Check if a message is in the current room.
1131 * This is used by CtdlFetchMessage to prevent random picking
1132 * of messages from users private rooms
1134 * The message list should probably be cached against the CC->room
1136 int CtdlMessageInRoom(long msgnum)
1139 struct cdbdata *cdbfr;
1141 /* Learn about the user and room in question */
1142 CtdlGetUser(&CC->user, CC->curr_user);
1143 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
1145 /* Load the message list */
1146 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
1147 if (cdbfr != NULL) {
1148 long *msglist = NULL;
1153 msglist = (long *) cdbfr->ptr;
1154 num_msgs = cdbfr->len / sizeof(long);
1156 /* search for message msgnum */
1157 for (i=0; i<num_msgs; i++) {
1158 if (msglist[i] == msgnum) {
1173 * Load a message from disk into memory.
1174 * This is used by CtdlOutputMsg() and other fetch functions.
1176 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1177 * using the CtdlMessageFree() function.
1179 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1181 struct cdbdata *dmsgtext;
1182 struct CtdlMessage *ret = NULL;
1186 cit_uint8_t field_header;
1188 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1190 #ifdef MESSAGE_IN_ROOM
1191 if (!CtdlMessageInRoom(msgnum)) {
1192 CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msgnum);
1197 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1198 if (dmsgtext == NULL) {
1201 mptr = dmsgtext->ptr;
1202 upper_bound = mptr + dmsgtext->len;
1204 /* Parse the three bytes that begin EVERY message on disk.
1205 * The first is always 0xFF, the on-disk magic number.
1206 * The second is the anonymous/public type byte.
1207 * The third is the format type byte (vari, fixed, or MIME).
1211 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1215 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1216 memset(ret, 0, sizeof(struct CtdlMessage));
1218 ret->cm_magic = CTDLMESSAGE_MAGIC;
1219 ret->cm_anon_type = *mptr++; /* Anon type byte */
1220 ret->cm_format_type = *mptr++; /* Format type byte */
1223 * The rest is zero or more arbitrary fields. Load them in.
1224 * We're done when we encounter either a zero-length field or
1225 * have just processed the 'M' (message text) field.
1228 if (mptr >= upper_bound) {
1231 field_header = *mptr++;
1232 ret->cm_fields[field_header] = strdup(mptr);
1234 while (*mptr++ != 0); /* advance to next field */
1236 } while ((mptr < upper_bound) && (field_header != 'M'));
1240 /* Always make sure there's something in the msg text field. If
1241 * it's NULL, the message text is most likely stored separately,
1242 * so go ahead and fetch that. Failing that, just set a dummy
1243 * body so other code doesn't barf.
1245 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1246 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1247 if (dmsgtext != NULL) {
1248 ret->cm_fields['M'] = dmsgtext->ptr;
1249 dmsgtext->ptr = NULL;
1253 if (ret->cm_fields['M'] == NULL) {
1254 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1257 /* Perform "before read" hooks (aborting if any return nonzero) */
1258 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1259 CtdlFreeMessage(ret);
1268 * Returns 1 if the supplied pointer points to a valid Citadel message.
1269 * If the pointer is NULL or the magic number check fails, returns 0.
1271 int is_valid_message(struct CtdlMessage *msg) {
1274 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1275 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1283 * 'Destructor' for struct CtdlMessage
1285 void CtdlFreeMessage(struct CtdlMessage *msg)
1289 if (is_valid_message(msg) == 0)
1291 if (msg != NULL) free (msg);
1295 for (i = 0; i < 256; ++i)
1296 if (msg->cm_fields[i] != NULL) {
1297 free(msg->cm_fields[i]);
1300 msg->cm_magic = 0; /* just in case */
1306 * Pre callback function for multipart/alternative
1308 * NOTE: this differs from the standard behavior for a reason. Normally when
1309 * displaying multipart/alternative you want to show the _last_ usable
1310 * format in the message. Here we show the _first_ one, because it's
1311 * usually text/plain. Since this set of functions is designed for text
1312 * output to non-MIME-aware clients, this is the desired behavior.
1315 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1316 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1317 char *cbid, void *cbuserdata)
1321 ma = (struct ma_info *)cbuserdata;
1322 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1323 if (!strcasecmp(cbtype, "multipart/alternative")) {
1327 if (!strcasecmp(cbtype, "message/rfc822")) {
1333 * Post callback function for multipart/alternative
1335 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1336 void *content, char *cbtype, char *cbcharset, size_t length,
1337 char *encoding, char *cbid, void *cbuserdata)
1341 ma = (struct ma_info *)cbuserdata;
1342 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1343 if (!strcasecmp(cbtype, "multipart/alternative")) {
1347 if (!strcasecmp(cbtype, "message/rfc822")) {
1353 * Inline callback function for mime parser that wants to display text
1355 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1356 void *content, char *cbtype, char *cbcharset, size_t length,
1357 char *encoding, char *cbid, void *cbuserdata)
1364 ma = (struct ma_info *)cbuserdata;
1366 CtdlLogPrintf(CTDL_DEBUG,
1367 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1368 partnum, filename, cbtype, (long)length);
1371 * If we're in the middle of a multipart/alternative scope and
1372 * we've already printed another section, skip this one.
1374 if ( (ma->is_ma) && (ma->did_print) ) {
1375 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1380 if ( (!strcasecmp(cbtype, "text/plain"))
1381 || (IsEmptyStr(cbtype)) ) {
1384 client_write(wptr, length);
1385 if (wptr[length-1] != '\n') {
1392 if (!strcasecmp(cbtype, "text/html")) {
1393 ptr = html_to_ascii(content, length, 80, 0);
1395 client_write(ptr, wlen);
1396 if (ptr[wlen-1] != '\n') {
1403 if (ma->use_fo_hooks) {
1404 if (PerformFixedOutputHooks(cbtype, content, length)) {
1405 /* above function returns nonzero if it handled the part */
1410 if (strncasecmp(cbtype, "multipart/", 10)) {
1411 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1412 partnum, filename, cbtype, (long)length);
1418 * The client is elegant and sophisticated and wants to be choosy about
1419 * MIME content types, so figure out which multipart/alternative part
1420 * we're going to send.
1422 * We use a system of weights. When we find a part that matches one of the
1423 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1424 * and then set ma->chosen_pref to that MIME type's position in our preference
1425 * list. If we then hit another match, we only replace the first match if
1426 * the preference value is lower.
1428 void choose_preferred(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)
1436 ma = (struct ma_info *)cbuserdata;
1438 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1439 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1440 // I don't know if there are any side effects! Please TEST TEST TEST
1441 //if (ma->is_ma > 0) {
1443 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1444 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1445 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1446 if (i < ma->chosen_pref) {
1447 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1448 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1449 ma->chosen_pref = i;
1456 * Now that we've chosen our preferred part, output it.
1458 void output_preferred(char *name,
1472 int add_newline = 0;
1475 char *decoded = NULL;
1476 size_t bytes_decoded;
1479 ma = (struct ma_info *)cbuserdata;
1481 /* This is not the MIME part you're looking for... */
1482 if (strcasecmp(partnum, ma->chosen_part)) return;
1484 /* If the content-type of this part is in our preferred formats
1485 * list, we can simply output it verbatim.
1487 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1488 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1489 if (!strcasecmp(buf, cbtype)) {
1490 /* Yeah! Go! W00t!! */
1491 if (ma->dont_decode == 0)
1492 rc = mime_decode_now (content,
1498 break; /* Give us the chance, maybe theres another one. */
1500 if (rc == 0) text_content = (char *)content;
1502 text_content = decoded;
1503 length = bytes_decoded;
1506 if (text_content[length-1] != '\n') {
1509 cprintf("Content-type: %s", cbtype);
1510 if (!IsEmptyStr(cbcharset)) {
1511 cprintf("; charset=%s", cbcharset);
1513 cprintf("\nContent-length: %d\n",
1514 (int)(length + add_newline) );
1515 if (!IsEmptyStr(encoding)) {
1516 cprintf("Content-transfer-encoding: %s\n", encoding);
1519 cprintf("Content-transfer-encoding: 7bit\n");
1521 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1523 if (client_write(text_content, length) == -1)
1525 CtdlLogPrintf(CTDL_ERR, "output_preferred(): aborting due to write failure.\n");
1528 if (add_newline) cprintf("\n");
1529 if (decoded != NULL) free(decoded);
1534 /* No translations required or possible: output as text/plain */
1535 cprintf("Content-type: text/plain\n\n");
1537 if (ma->dont_decode == 0)
1538 rc = mime_decode_now (content,
1544 return; /* Give us the chance, maybe theres another one. */
1546 if (rc == 0) text_content = (char *)content;
1548 text_content = decoded;
1549 length = bytes_decoded;
1552 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1553 length, encoding, cbid, cbuserdata);
1554 if (decoded != NULL) free(decoded);
1559 char desired_section[64];
1566 * Callback function for
1568 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1569 void *content, char *cbtype, char *cbcharset, size_t length,
1570 char *encoding, char *cbid, void *cbuserdata)
1572 struct encapmsg *encap;
1574 encap = (struct encapmsg *)cbuserdata;
1576 /* Only proceed if this is the desired section... */
1577 if (!strcasecmp(encap->desired_section, partnum)) {
1578 encap->msglen = length;
1579 encap->msg = malloc(length + 2);
1580 memcpy(encap->msg, content, length);
1587 * Determine whether the currently logged in session has permission to read
1588 * messages in the current room.
1590 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1591 if ( (!(CC->logged_in))
1592 && (!(CC->internal_pgm))
1593 && (!config.c_guest_logins)
1595 return(om_not_logged_in);
1602 * Get a message off disk. (returns om_* values found in msgbase.h)
1605 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1606 int mode, /* how would you like that message? */
1607 int headers_only, /* eschew the message body? */
1608 int do_proto, /* do Citadel protocol responses? */
1609 int crlf, /* Use CRLF newlines instead of LF? */
1610 char *section, /* NULL or a message/rfc822 section */
1611 int flags /* various flags; see msgbase.h */
1613 struct CtdlMessage *TheMessage = NULL;
1614 int retcode = om_no_such_msg;
1615 struct encapmsg encap;
1618 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1620 (section ? section : "<>")
1623 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1626 if (r == om_not_logged_in) {
1627 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1630 cprintf("%d An unknown error has occurred.\n", ERROR);
1636 #ifdef MESSAGE_IN_ROOM
1637 if (!CtdlMessageInRoom(msg_num)) {
1638 CtdlLogPrintf(CTDL_DEBUG, "Message %ld not in current room\n", msg_num);
1639 if (do_proto) cprintf("%d Can't locate msg %ld in room\n",
1640 ERROR + MESSAGE_NOT_FOUND, msg_num);
1641 return(om_no_such_msg);
1646 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1647 * request that we don't even bother loading the body into memory.
1649 if (headers_only == HEADERS_FAST) {
1650 TheMessage = CtdlFetchMessage(msg_num, 0);
1653 TheMessage = CtdlFetchMessage(msg_num, 1);
1656 if (TheMessage == NULL) {
1657 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1658 ERROR + MESSAGE_NOT_FOUND, msg_num);
1659 return(om_no_such_msg);
1662 /* Here is the weird form of this command, to process only an
1663 * encapsulated message/rfc822 section.
1665 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1666 memset(&encap, 0, sizeof encap);
1667 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1668 mime_parser(TheMessage->cm_fields['M'],
1670 *extract_encapsulated_message,
1671 NULL, NULL, (void *)&encap, 0
1673 CtdlFreeMessage(TheMessage);
1677 encap.msg[encap.msglen] = 0;
1678 TheMessage = convert_internet_message(encap.msg);
1679 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1681 /* Now we let it fall through to the bottom of this
1682 * function, because TheMessage now contains the
1683 * encapsulated message instead of the top-level
1684 * message. Isn't that neat?
1689 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1690 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1691 retcode = om_no_such_msg;
1696 /* Ok, output the message now */
1697 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1698 CtdlFreeMessage(TheMessage);
1704 char *qp_encode_email_addrs(char *source)
1706 char *user, *node, *name;
1707 const char headerStr[] = "=?UTF-8?Q?";
1711 int need_to_encode = 0;
1717 long nAddrPtrMax = 50;
1722 if (source == NULL) return source;
1723 if (IsEmptyStr(source)) return source;
1725 CtdlLogPrintf(CTDL_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1727 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1728 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1729 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1732 while (!IsEmptyStr (&source[i])) {
1733 if (nColons >= nAddrPtrMax){
1736 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1737 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1738 free (AddrPtr), AddrPtr = ptr;
1740 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1741 memset(&ptr[nAddrPtrMax], 0,
1742 sizeof (long) * nAddrPtrMax);
1744 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1745 free (AddrUtf8), AddrUtf8 = ptr;
1748 if (((unsigned char) source[i] < 32) ||
1749 ((unsigned char) source[i] > 126)) {
1751 AddrUtf8[nColons] = 1;
1753 if (source[i] == '"')
1754 InQuotes = !InQuotes;
1755 if (!InQuotes && source[i] == ',') {
1756 AddrPtr[nColons] = i;
1761 if (need_to_encode == 0) {
1768 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1769 Encoded = (char*) malloc (EncodedMaxLen);
1771 for (i = 0; i < nColons; i++)
1772 source[AddrPtr[i]++] = '\0';
1773 /* TODO: if libidn, this might get larger*/
1774 user = malloc(SourceLen + 1);
1775 node = malloc(SourceLen + 1);
1776 name = malloc(SourceLen + 1);
1780 for (i = 0; i < nColons && nPtr != NULL; i++) {
1781 nmax = EncodedMaxLen - (nPtr - Encoded);
1783 process_rfc822_addr(&source[AddrPtr[i]],
1787 /* TODO: libIDN here ! */
1788 if (IsEmptyStr(name)) {
1789 n = snprintf(nPtr, nmax,
1790 (i==0)?"%s@%s" : ",%s@%s",
1794 EncodedName = rfc2047encode(name, strlen(name));
1795 n = snprintf(nPtr, nmax,
1796 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1797 EncodedName, user, node);
1802 n = snprintf(nPtr, nmax,
1803 (i==0)?"%s" : ",%s",
1804 &source[AddrPtr[i]]);
1810 ptr = (char*) malloc(EncodedMaxLen * 2);
1811 memcpy(ptr, Encoded, EncodedMaxLen);
1812 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1813 free(Encoded), Encoded = ptr;
1815 i--; /* do it once more with properly lengthened buffer */
1818 for (i = 0; i < nColons; i++)
1819 source[--AddrPtr[i]] = ',';
1830 /* If the last item in a list of recipients was truncated to a partial address,
1831 * remove it completely in order to avoid choking libSieve
1833 void sanitize_truncated_recipient(char *str)
1836 if (num_tokens(str, ',') < 2) return;
1838 int len = strlen(str);
1839 if (len < 900) return;
1840 if (len > 998) str[998] = 0;
1842 char *cptr = strrchr(str, ',');
1845 char *lptr = strchr(cptr, '<');
1846 char *rptr = strchr(cptr, '>');
1848 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1854 void OutputCtdlMsgHeaders(
1855 struct CtdlMessage *TheMessage,
1856 int do_proto) /* do Citadel protocol responses? */
1862 char display_name[256];
1864 /* begin header processing loop for Citadel message format */
1865 safestrncpy(display_name, "<unknown>", sizeof display_name);
1866 if (TheMessage->cm_fields['A']) {
1867 strcpy(buf, TheMessage->cm_fields['A']);
1868 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1869 safestrncpy(display_name, "****", sizeof display_name);
1871 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1872 safestrncpy(display_name, "anonymous", sizeof display_name);
1875 safestrncpy(display_name, buf, sizeof display_name);
1877 if ((is_room_aide())
1878 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1879 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1880 size_t tmp = strlen(display_name);
1881 snprintf(&display_name[tmp],
1882 sizeof display_name - tmp,
1887 /* Don't show Internet address for users on the
1888 * local Citadel network.
1891 if (TheMessage->cm_fields['N'] != NULL)
1892 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1893 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1897 /* Now spew the header fields in the order we like them. */
1898 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1899 for (i=0; i<n; ++i) {
1900 k = (int) allkeys[i];
1902 if ( (TheMessage->cm_fields[k] != NULL)
1903 && (msgkeys[k] != NULL) ) {
1904 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1905 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1908 if (do_proto) cprintf("%s=%s\n",
1912 else if ((k == 'F') && (suppress_f)) {
1915 /* Masquerade display name if needed */
1917 if (do_proto) cprintf("%s=%s\n",
1919 TheMessage->cm_fields[k]
1928 void OutputRFC822MsgHeaders(
1929 struct CtdlMessage *TheMessage,
1930 int flags, /* should the bessage be exported clean */
1932 char *mid, long sizeof_mid,
1933 char *suser, long sizeof_suser,
1934 char *luser, long sizeof_luser,
1935 char *fuser, long sizeof_fuser,
1936 char *snode, long sizeof_snode)
1938 char datestamp[100];
1939 int subject_found = 0;
1945 for (i = 0; i < 256; ++i) {
1946 if (TheMessage->cm_fields[i]) {
1947 mptr = mpptr = TheMessage->cm_fields[i];
1950 safestrncpy(luser, mptr, sizeof_luser);
1951 safestrncpy(suser, mptr, sizeof_suser);
1953 else if (i == 'Y') {
1954 if ((flags & QP_EADDR) != 0) {
1955 mptr = qp_encode_email_addrs(mptr);
1957 sanitize_truncated_recipient(mptr);
1958 cprintf("CC: %s%s", mptr, nl);
1960 else if (i == 'P') {
1961 cprintf("Return-Path: %s%s", mptr, nl);
1963 else if (i == 'L') {
1964 cprintf("List-ID: %s%s", mptr, nl);
1966 else if (i == 'V') {
1967 if ((flags & QP_EADDR) != 0)
1968 mptr = qp_encode_email_addrs(mptr);
1969 cprintf("Envelope-To: %s%s", mptr, nl);
1971 else if (i == 'U') {
1972 cprintf("Subject: %s%s", mptr, nl);
1976 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1978 safestrncpy(fuser, mptr, sizeof_fuser);
1979 /* else if (i == 'O')
1980 cprintf("X-Citadel-Room: %s%s",
1983 safestrncpy(snode, mptr, sizeof_snode);
1986 if (haschar(mptr, '@') == 0)
1988 sanitize_truncated_recipient(mptr);
1989 cprintf("To: %s@%s", mptr, config.c_fqdn);
1994 if ((flags & QP_EADDR) != 0) {
1995 mptr = qp_encode_email_addrs(mptr);
1997 sanitize_truncated_recipient(mptr);
1998 cprintf("To: %s", mptr);
2002 else if (i == 'T') {
2003 datestring(datestamp, sizeof datestamp,
2004 atol(mptr), DATESTRING_RFC822);
2005 cprintf("Date: %s%s", datestamp, nl);
2007 else if (i == 'W') {
2008 cprintf("References: ");
2009 k = num_tokens(mptr, '|');
2010 for (j=0; j<k; ++j) {
2011 extract_token(buf, mptr, j, '|', sizeof buf);
2012 cprintf("<%s>", buf);
2021 else if (i == 'K') {
2022 cprintf("Reply-To: <%s>%s", mptr, nl);
2028 if (subject_found == 0) {
2029 cprintf("Subject: (no subject)%s", nl);
2034 void Dump_RFC822HeadersBody(
2035 struct CtdlMessage *TheMessage,
2036 int headers_only, /* eschew the message body? */
2037 int flags, /* should the bessage be exported clean? */
2041 cit_uint8_t prev_ch;
2043 const char *StartOfText = StrBufNOTNULL;
2046 int nllen = strlen(nl);
2050 mptr = TheMessage->cm_fields['M'];
2054 while (*mptr != '\0') {
2055 if (*mptr == '\r') {
2062 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2064 eoh = *(mptr+1) == '\n';
2068 StartOfText = strchr(StartOfText, '\n');
2069 StartOfText = strchr(StartOfText, '\n');
2072 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2073 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2074 ((headers_only != HEADERS_NONE) &&
2075 (headers_only != HEADERS_ONLY))
2077 if (*mptr == '\n') {
2078 memcpy(&outbuf[outlen], nl, nllen);
2080 outbuf[outlen] = '\0';
2083 outbuf[outlen++] = *mptr;
2087 if (flags & ESC_DOT)
2089 if ((prev_ch == '\n') &&
2091 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2093 outbuf[outlen++] = '.';
2098 if (outlen > 1000) {
2099 if (client_write(outbuf, outlen) == -1)
2101 CtdlLogPrintf(CTDL_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2108 rc = client_write(outbuf, outlen);
2115 /* If the format type on disk is 1 (fixed-format), then we want
2116 * everything to be output completely literally ... regardless of
2117 * what message transfer format is in use.
2119 void DumpFormatFixed(
2120 struct CtdlMessage *TheMessage,
2121 int mode, /* how would you like that message? */
2128 int nllen = strlen (nl);
2131 mptr = TheMessage->cm_fields['M'];
2133 if (mode == MT_MIME) {
2134 cprintf("Content-type: text/plain\n\n");
2138 while (ch = *mptr++, ch > 0) {
2142 if ((buflen > 250) && (!xlline)){
2146 while ((buflen > 0) &&
2147 (!isspace(buf[buflen])))
2153 mptr -= tbuflen - buflen;
2158 /* if we reach the outer bounds of our buffer,
2159 abort without respect what whe purge. */
2162 (buflen > SIZ - nllen - 2)))
2166 memcpy (&buf[buflen], nl, nllen);
2170 if (client_write(buf, buflen) == -1)
2172 CtdlLogPrintf(CTDL_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2184 if (!IsEmptyStr(buf))
2185 cprintf("%s%s", buf, nl);
2189 * Get a message off disk. (returns om_* values found in msgbase.h)
2191 int CtdlOutputPreLoadedMsg(
2192 struct CtdlMessage *TheMessage,
2193 int mode, /* how would you like that message? */
2194 int headers_only, /* eschew the message body? */
2195 int do_proto, /* do Citadel protocol responses? */
2196 int crlf, /* Use CRLF newlines instead of LF? */
2197 int flags /* should the bessage be exported clean? */
2201 const char *nl; /* newline string */
2204 /* Buffers needed for RFC822 translation. These are all filled
2205 * using functions that are bounds-checked, and therefore we can
2206 * make them substantially smaller than SIZ.
2214 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2215 ((TheMessage == NULL) ? "NULL" : "not null"),
2216 mode, headers_only, do_proto, crlf);
2218 strcpy(mid, "unknown");
2219 nl = (crlf ? "\r\n" : "\n");
2221 if (!is_valid_message(TheMessage)) {
2222 CtdlLogPrintf(CTDL_ERR,
2223 "ERROR: invalid preloaded message for output\n");
2225 return(om_no_such_msg);
2228 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2229 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2231 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2232 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2235 /* Are we downloading a MIME component? */
2236 if (mode == MT_DOWNLOAD) {
2237 if (TheMessage->cm_format_type != FMT_RFC822) {
2239 cprintf("%d This is not a MIME message.\n",
2240 ERROR + ILLEGAL_VALUE);
2241 } else if (CC->download_fp != NULL) {
2242 if (do_proto) cprintf(
2243 "%d You already have a download open.\n",
2244 ERROR + RESOURCE_BUSY);
2246 /* Parse the message text component */
2247 mptr = TheMessage->cm_fields['M'];
2248 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2249 /* If there's no file open by this time, the requested
2250 * section wasn't found, so print an error
2252 if (CC->download_fp == NULL) {
2253 if (do_proto) cprintf(
2254 "%d Section %s not found.\n",
2255 ERROR + FILE_NOT_FOUND,
2256 CC->download_desired_section);
2259 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2262 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2263 * in a single server operation instead of opening a download file.
2265 if (mode == MT_SPEW_SECTION) {
2266 if (TheMessage->cm_format_type != FMT_RFC822) {
2268 cprintf("%d This is not a MIME message.\n",
2269 ERROR + ILLEGAL_VALUE);
2271 /* Parse the message text component */
2274 mptr = TheMessage->cm_fields['M'];
2275 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2276 /* If section wasn't found, print an error
2279 if (do_proto) cprintf(
2280 "%d Section %s not found.\n",
2281 ERROR + FILE_NOT_FOUND,
2282 CC->download_desired_section);
2285 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2288 /* now for the user-mode message reading loops */
2289 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2291 /* Does the caller want to skip the headers? */
2292 if (headers_only == HEADERS_NONE) goto START_TEXT;
2294 /* Tell the client which format type we're using. */
2295 if ( (mode == MT_CITADEL) && (do_proto) ) {
2296 cprintf("type=%d\n", TheMessage->cm_format_type);
2299 /* nhdr=yes means that we're only displaying headers, no body */
2300 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2301 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2304 cprintf("nhdr=yes\n");
2307 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2308 OutputCtdlMsgHeaders(TheMessage, do_proto);
2311 /* begin header processing loop for RFC822 transfer format */
2315 strcpy(snode, NODENAME);
2316 if (mode == MT_RFC822)
2317 OutputRFC822MsgHeaders(
2322 suser, sizeof(suser),
2323 luser, sizeof(luser),
2324 fuser, sizeof(fuser),
2325 snode, sizeof(snode)
2329 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2330 suser[i] = tolower(suser[i]);
2331 if (!isalnum(suser[i])) suser[i]='_';
2334 if (mode == MT_RFC822) {
2335 if (!strcasecmp(snode, NODENAME)) {
2336 safestrncpy(snode, FQDN, sizeof snode);
2339 /* Construct a fun message id */
2340 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2341 if (strchr(mid, '@')==NULL) {
2342 cprintf("@%s", snode);
2346 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2347 cprintf("From: \"----\" <x@x.org>%s", nl);
2349 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2350 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2352 else if (!IsEmptyStr(fuser)) {
2353 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2356 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2359 /* Blank line signifying RFC822 end-of-headers */
2360 if (TheMessage->cm_format_type != FMT_RFC822) {
2365 /* end header processing loop ... at this point, we're in the text */
2367 if (headers_only == HEADERS_FAST) goto DONE;
2369 /* Tell the client about the MIME parts in this message */
2370 if (TheMessage->cm_format_type == FMT_RFC822) {
2371 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2372 mptr = TheMessage->cm_fields['M'];
2373 memset(&ma, 0, sizeof(struct ma_info));
2374 mime_parser(mptr, NULL,
2375 (do_proto ? *list_this_part : NULL),
2376 (do_proto ? *list_this_pref : NULL),
2377 (do_proto ? *list_this_suff : NULL),
2380 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2381 Dump_RFC822HeadersBody(
2390 if (headers_only == HEADERS_ONLY) {
2394 /* signify start of msg text */
2395 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2396 if (do_proto) cprintf("text\n");
2399 if (TheMessage->cm_format_type == FMT_FIXED)
2402 mode, /* how would you like that message? */
2405 /* If the message on disk is format 0 (Citadel vari-format), we
2406 * output using the formatter at 80 columns. This is the final output
2407 * form if the transfer format is RFC822, but if the transfer format
2408 * is Citadel proprietary, it'll still work, because the indentation
2409 * for new paragraphs is correct and the client will reformat the
2410 * message to the reader's screen width.
2412 if (TheMessage->cm_format_type == FMT_CITADEL) {
2413 mptr = TheMessage->cm_fields['M'];
2415 if (mode == MT_MIME) {
2416 cprintf("Content-type: text/x-citadel-variformat\n\n");
2421 /* If the message on disk is format 4 (MIME), we've gotta hand it
2422 * off to the MIME parser. The client has already been told that
2423 * this message is format 1 (fixed format), so the callback function
2424 * we use will display those parts as-is.
2426 if (TheMessage->cm_format_type == FMT_RFC822) {
2427 memset(&ma, 0, sizeof(struct ma_info));
2429 if (mode == MT_MIME) {
2430 ma.use_fo_hooks = 0;
2431 strcpy(ma.chosen_part, "1");
2432 ma.chosen_pref = 9999;
2433 ma.dont_decode = CC->msg4_dont_decode;
2434 mime_parser(mptr, NULL,
2435 *choose_preferred, *fixed_output_pre,
2436 *fixed_output_post, (void *)&ma, 1);
2437 mime_parser(mptr, NULL,
2438 *output_preferred, NULL, NULL, (void *)&ma, 1);
2441 ma.use_fo_hooks = 1;
2442 mime_parser(mptr, NULL,
2443 *fixed_output, *fixed_output_pre,
2444 *fixed_output_post, (void *)&ma, 0);
2449 DONE: /* now we're done */
2450 if (do_proto) cprintf("000\n");
2456 * display a message (mode 0 - Citadel proprietary)
2458 void cmd_msg0(char *cmdbuf)
2461 int headers_only = HEADERS_ALL;
2463 msgid = extract_long(cmdbuf, 0);
2464 headers_only = extract_int(cmdbuf, 1);
2466 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2472 * display a message (mode 2 - RFC822)
2474 void cmd_msg2(char *cmdbuf)
2477 int headers_only = HEADERS_ALL;
2479 msgid = extract_long(cmdbuf, 0);
2480 headers_only = extract_int(cmdbuf, 1);
2482 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2488 * display a message (mode 3 - IGnet raw format - internal programs only)
2490 void cmd_msg3(char *cmdbuf)
2493 struct CtdlMessage *msg = NULL;
2496 if (CC->internal_pgm == 0) {
2497 cprintf("%d This command is for internal programs only.\n",
2498 ERROR + HIGHER_ACCESS_REQUIRED);
2502 msgnum = extract_long(cmdbuf, 0);
2503 msg = CtdlFetchMessage(msgnum, 1);
2505 cprintf("%d Message %ld not found.\n",
2506 ERROR + MESSAGE_NOT_FOUND, msgnum);
2510 serialize_message(&smr, msg);
2511 CtdlFreeMessage(msg);
2514 cprintf("%d Unable to serialize message\n",
2515 ERROR + INTERNAL_ERROR);
2519 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2520 client_write((char *)smr.ser, (int)smr.len);
2527 * Display a message using MIME content types
2529 void cmd_msg4(char *cmdbuf)
2534 msgid = extract_long(cmdbuf, 0);
2535 extract_token(section, cmdbuf, 1, '|', sizeof section);
2536 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2542 * Client tells us its preferred message format(s)
2544 void cmd_msgp(char *cmdbuf)
2546 if (!strcasecmp(cmdbuf, "dont_decode")) {
2547 CC->msg4_dont_decode = 1;
2548 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2551 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2552 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2558 * Open a component of a MIME message as a download file
2560 void cmd_opna(char *cmdbuf)
2563 char desired_section[128];
2565 msgid = extract_long(cmdbuf, 0);
2566 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2567 safestrncpy(CC->download_desired_section, desired_section,
2568 sizeof CC->download_desired_section);
2569 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2574 * Open a component of a MIME message and transmit it all at once
2576 void cmd_dlat(char *cmdbuf)
2579 char desired_section[128];
2581 msgid = extract_long(cmdbuf, 0);
2582 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2583 safestrncpy(CC->download_desired_section, desired_section,
2584 sizeof CC->download_desired_section);
2585 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2590 * Save one or more message pointers into a specified room
2591 * (Returns 0 for success, nonzero for failure)
2592 * roomname may be NULL to use the current room
2594 * Note that the 'supplied_msg' field may be set to NULL, in which case
2595 * the message will be fetched from disk, by number, if we need to perform
2596 * replication checks. This adds an additional database read, so if the
2597 * caller already has the message in memory then it should be supplied. (Obviously
2598 * this mode of operation only works if we're saving a single message.)
2600 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2601 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2604 char hold_rm[ROOMNAMELEN];
2605 struct cdbdata *cdbfr;
2608 long highest_msg = 0L;
2611 struct CtdlMessage *msg = NULL;
2613 long *msgs_to_be_merged = NULL;
2614 int num_msgs_to_be_merged = 0;
2616 CtdlLogPrintf(CTDL_DEBUG,
2617 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2618 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2621 strcpy(hold_rm, CC->room.QRname);
2624 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2625 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2626 if (num_newmsgs > 1) supplied_msg = NULL;
2628 /* Now the regular stuff */
2629 if (CtdlGetRoomLock(&CC->room,
2630 ((roomname != NULL) ? roomname : CC->room.QRname) )
2632 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2633 return(ERROR + ROOM_NOT_FOUND);
2637 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2638 num_msgs_to_be_merged = 0;
2641 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2642 if (cdbfr == NULL) {
2646 msglist = (long *) cdbfr->ptr;
2647 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2648 num_msgs = cdbfr->len / sizeof(long);
2653 /* Create a list of msgid's which were supplied by the caller, but do
2654 * not already exist in the target room. It is absolutely taboo to
2655 * have more than one reference to the same message in a room.
2657 for (i=0; i<num_newmsgs; ++i) {
2659 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2660 if (msglist[j] == newmsgidlist[i]) {
2665 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2669 CtdlLogPrintf(CTDL_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2672 * Now merge the new messages
2674 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2675 if (msglist == NULL) {
2676 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2678 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2679 num_msgs += num_msgs_to_be_merged;
2681 /* Sort the message list, so all the msgid's are in order */
2682 num_msgs = sort_msglist(msglist, num_msgs);
2684 /* Determine the highest message number */
2685 highest_msg = msglist[num_msgs - 1];
2687 /* Write it back to disk. */
2688 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2689 msglist, (int)(num_msgs * sizeof(long)));
2691 /* Free up the memory we used. */
2694 /* Update the highest-message pointer and unlock the room. */
2695 CC->room.QRhighest = highest_msg;
2696 CtdlPutRoomLock(&CC->room);
2698 /* Perform replication checks if necessary */
2699 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2700 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2702 for (i=0; i<num_msgs_to_be_merged; ++i) {
2703 msgid = msgs_to_be_merged[i];
2705 if (supplied_msg != NULL) {
2709 msg = CtdlFetchMessage(msgid, 0);
2713 ReplicationChecks(msg);
2715 /* If the message has an Exclusive ID, index that... */
2716 if (msg->cm_fields['E'] != NULL) {
2717 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2720 /* Free up the memory we may have allocated */
2721 if (msg != supplied_msg) {
2722 CtdlFreeMessage(msg);
2730 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2733 /* Submit this room for processing by hooks */
2734 PerformRoomHooks(&CC->room);
2736 /* Go back to the room we were in before we wandered here... */
2737 CtdlGetRoom(&CC->room, hold_rm);
2739 /* Bump the reference count for all messages which were merged */
2740 if (!suppress_refcount_adj) {
2741 for (i=0; i<num_msgs_to_be_merged; ++i) {
2742 AdjRefCount(msgs_to_be_merged[i], +1);
2746 /* Free up memory... */
2747 if (msgs_to_be_merged != NULL) {
2748 free(msgs_to_be_merged);
2751 /* Return success. */
2757 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2760 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2761 int do_repl_check, struct CtdlMessage *supplied_msg)
2763 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2770 * Message base operation to save a new message to the message store
2771 * (returns new message number)
2773 * This is the back end for CtdlSubmitMsg() and should not be directly
2774 * called by server-side modules.
2777 long send_message(struct CtdlMessage *msg) {
2785 /* Get a new message number */
2786 newmsgid = get_new_message_number();
2787 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2789 /* Generate an ID if we don't have one already */
2790 if (msg->cm_fields['I']==NULL) {
2791 msg->cm_fields['I'] = strdup(msgidbuf);
2794 /* If the message is big, set its body aside for storage elsewhere */
2795 if (msg->cm_fields['M'] != NULL) {
2796 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2798 holdM = msg->cm_fields['M'];
2799 msg->cm_fields['M'] = NULL;
2803 /* Serialize our data structure for storage in the database */
2804 serialize_message(&smr, msg);
2807 msg->cm_fields['M'] = holdM;
2811 cprintf("%d Unable to serialize message\n",
2812 ERROR + INTERNAL_ERROR);
2816 /* Write our little bundle of joy into the message base */
2817 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2818 smr.ser, smr.len) < 0) {
2819 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2823 cdb_store(CDB_BIGMSGS,
2833 /* Free the memory we used for the serialized message */
2836 /* Return the *local* message ID to the caller
2837 * (even if we're storing an incoming network message)
2845 * Serialize a struct CtdlMessage into the format used on disk and network.
2847 * This function loads up a "struct ser_ret" (defined in server.h) which
2848 * contains the length of the serialized message and a pointer to the
2849 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2851 void serialize_message(struct ser_ret *ret, /* return values */
2852 struct CtdlMessage *msg) /* unserialized msg */
2854 size_t wlen, fieldlen;
2856 static char *forder = FORDER;
2859 * Check for valid message format
2861 if (is_valid_message(msg) == 0) {
2862 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2869 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2870 ret->len = ret->len +
2871 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2873 ret->ser = malloc(ret->len);
2874 if (ret->ser == NULL) {
2875 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2876 (long)ret->len, strerror(errno));
2883 ret->ser[1] = msg->cm_anon_type;
2884 ret->ser[2] = msg->cm_format_type;
2887 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2888 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2889 ret->ser[wlen++] = (char)forder[i];
2890 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2891 wlen = wlen + fieldlen + 1;
2893 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2894 (long)ret->len, (long)wlen);
2901 * Serialize a struct CtdlMessage into the format used on disk and network.
2903 * This function loads up a "struct ser_ret" (defined in server.h) which
2904 * contains the length of the serialized message and a pointer to the
2905 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2907 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2908 long Siz) /* how many chars ? */
2912 static char *forder = FORDER;
2916 * Check for valid message format
2918 if (is_valid_message(msg) == 0) {
2919 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2923 buf = (char*) malloc (Siz + 1);
2927 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2928 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2929 msg->cm_fields[(int)forder[i]]);
2930 if (client_write (buf, strlen(buf)) == -1)
2932 CtdlLogPrintf(CTDL_ERR, "dump_message(): aborting due to write failure.\n");
2943 * Check to see if any messages already exist in the current room which
2944 * carry the same Exclusive ID as this one. If any are found, delete them.
2946 void ReplicationChecks(struct CtdlMessage *msg) {
2947 long old_msgnum = (-1L);
2949 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2951 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2954 /* No exclusive id? Don't do anything. */
2955 if (msg == NULL) return;
2956 if (msg->cm_fields['E'] == NULL) return;
2957 if (IsEmptyStr(msg->cm_fields['E'])) return;
2958 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2959 msg->cm_fields['E'], CC->room.QRname);*/
2961 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2962 if (old_msgnum > 0L) {
2963 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2964 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2971 * Save a message to disk and submit it into the delivery system.
2973 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2974 struct recptypes *recps, /* recipients (if mail) */
2975 const char *force, /* force a particular room? */
2976 int flags /* should the message be exported clean? */
2978 char submit_filename[128];
2979 char generated_timestamp[32];
2980 char hold_rm[ROOMNAMELEN];
2981 char actual_rm[ROOMNAMELEN];
2982 char force_room[ROOMNAMELEN];
2983 char content_type[SIZ]; /* We have to learn this */
2984 char recipient[SIZ];
2986 const char *mptr = NULL;
2987 struct ctdluser userbuf;
2989 struct MetaData smi;
2990 FILE *network_fp = NULL;
2991 static int seqnum = 1;
2992 struct CtdlMessage *imsg = NULL;
2994 size_t instr_alloc = 0;
2996 char *hold_R, *hold_D;
2997 char *collected_addresses = NULL;
2998 struct addresses_to_be_filed *aptr = NULL;
2999 StrBuf *saved_rfc822_version = NULL;
3000 int qualified_for_journaling = 0;
3001 CitContext *CCC = MyContext();
3002 char bounce_to[1024] = "";
3006 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
3007 if (is_valid_message(msg) == 0) return(-1); /* self check */
3009 /* If this message has no timestamp, we take the liberty of
3010 * giving it one, right now.
3012 if (msg->cm_fields['T'] == NULL) {
3013 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3014 msg->cm_fields['T'] = strdup(generated_timestamp);
3017 /* If this message has no path, we generate one.
3019 if (msg->cm_fields['P'] == NULL) {
3020 if (msg->cm_fields['A'] != NULL) {
3021 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3022 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3023 if (isspace(msg->cm_fields['P'][a])) {
3024 msg->cm_fields['P'][a] = ' ';
3029 msg->cm_fields['P'] = strdup("unknown");
3033 if (force == NULL) {
3034 strcpy(force_room, "");
3037 strcpy(force_room, force);
3040 /* Learn about what's inside, because it's what's inside that counts */
3041 if (msg->cm_fields['M'] == NULL) {
3042 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
3046 switch (msg->cm_format_type) {
3048 strcpy(content_type, "text/x-citadel-variformat");
3051 strcpy(content_type, "text/plain");
3054 strcpy(content_type, "text/plain");
3055 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3058 safestrncpy(content_type, &mptr[13], sizeof content_type);
3059 striplt(content_type);
3060 aptr = content_type;
3061 while (!IsEmptyStr(aptr)) {
3073 /* Goto the correct room */
3074 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3075 strcpy(hold_rm, CCC->room.QRname);
3076 strcpy(actual_rm, CCC->room.QRname);
3077 if (recps != NULL) {
3078 strcpy(actual_rm, SENTITEMS);
3081 /* If the user is a twit, move to the twit room for posting */
3083 if (CCC->user.axlevel == AxProbU) {
3084 strcpy(hold_rm, actual_rm);
3085 strcpy(actual_rm, config.c_twitroom);
3086 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
3090 /* ...or if this message is destined for Aide> then go there. */
3091 if (!IsEmptyStr(force_room)) {
3092 strcpy(actual_rm, force_room);
3095 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
3096 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3097 /* CtdlGetRoom(&CCC->room, actual_rm); */
3098 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3102 * If this message has no O (room) field, generate one.
3104 if (msg->cm_fields['O'] == NULL) {
3105 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3108 /* Perform "before save" hooks (aborting if any return nonzero) */
3109 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
3110 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3113 * If this message has an Exclusive ID, and the room is replication
3114 * checking enabled, then do replication checks.
3116 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3117 ReplicationChecks(msg);
3120 /* Save it to disk */
3121 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
3122 newmsgid = send_message(msg);
3123 if (newmsgid <= 0L) return(-5);
3125 /* Write a supplemental message info record. This doesn't have to
3126 * be a critical section because nobody else knows about this message
3129 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
3130 memset(&smi, 0, sizeof(struct MetaData));
3131 smi.meta_msgnum = newmsgid;
3132 smi.meta_refcount = 0;
3133 safestrncpy(smi.meta_content_type, content_type,
3134 sizeof smi.meta_content_type);
3137 * Measure how big this message will be when rendered as RFC822.
3138 * We do this for two reasons:
3139 * 1. We need the RFC822 length for the new metadata record, so the
3140 * POP and IMAP services don't have to calculate message lengths
3141 * while the user is waiting (multiplied by potentially hundreds
3142 * or thousands of messages).
3143 * 2. If journaling is enabled, we will need an RFC822 version of the
3144 * message to attach to the journalized copy.
3146 if (CCC->redirect_buffer != NULL) {
3147 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3150 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3151 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3152 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3153 saved_rfc822_version = CCC->redirect_buffer;
3154 CCC->redirect_buffer = NULL;
3158 /* Now figure out where to store the pointers */
3159 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3161 /* If this is being done by the networker delivering a private
3162 * message, we want to BYPASS saving the sender's copy (because there
3163 * is no local sender; it would otherwise go to the Trashcan).
3165 if ((!CCC->internal_pgm) || (recps == NULL)) {
3166 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3167 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3168 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3172 /* For internet mail, drop a copy in the outbound queue room */
3173 if ((recps != NULL) && (recps->num_internet > 0)) {
3174 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3177 /* If other rooms are specified, drop them there too. */
3178 if ((recps != NULL) && (recps->num_room > 0))
3179 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3180 extract_token(recipient, recps->recp_room, i,
3181 '|', sizeof recipient);
3182 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3183 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3186 /* Bump this user's messages posted counter. */
3187 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3188 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3189 CCC->user.posted = CCC->user.posted + 1;
3190 CtdlPutUserLock(&CCC->user);
3192 /* Decide where bounces need to be delivered */
3193 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3194 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3196 else if (CCC->logged_in) {
3197 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3200 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3203 /* If this is private, local mail, make a copy in the
3204 * recipient's mailbox and bump the reference count.
3206 if ((recps != NULL) && (recps->num_local > 0))
3207 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3208 extract_token(recipient, recps->recp_local, i,
3209 '|', sizeof recipient);
3210 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3212 if (CtdlGetUser(&userbuf, recipient) == 0) {
3213 // Add a flag so the Funambol module knows its mail
3214 msg->cm_fields['W'] = strdup(recipient);
3215 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3216 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3217 CtdlBumpNewMailCounter(userbuf.usernum);
3218 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3219 /* Generate a instruction message for the Funambol notification
3220 * server, in the same style as the SMTP queue
3223 instr = malloc(instr_alloc);
3224 snprintf(instr, instr_alloc,
3225 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3227 SPOOLMIME, newmsgid, (long)time(NULL),
3231 imsg = malloc(sizeof(struct CtdlMessage));
3232 memset(imsg, 0, sizeof(struct CtdlMessage));
3233 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3234 imsg->cm_anon_type = MES_NORMAL;
3235 imsg->cm_format_type = FMT_RFC822;
3236 imsg->cm_fields['A'] = strdup("Citadel");
3237 imsg->cm_fields['J'] = strdup("do not journal");
3238 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3239 imsg->cm_fields['W'] = strdup(recipient);
3240 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3241 CtdlFreeMessage(imsg);
3245 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3246 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3251 /* Perform "after save" hooks */
3252 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3253 PerformMessageHooks(msg, EVT_AFTERSAVE);
3255 /* For IGnet mail, we have to save a new copy into the spooler for
3256 * each recipient, with the R and D fields set to the recipient and
3257 * destination-node. This has two ugly side effects: all other
3258 * recipients end up being unlisted in this recipient's copy of the
3259 * message, and it has to deliver multiple messages to the same
3260 * node. We'll revisit this again in a year or so when everyone has
3261 * a network spool receiver that can handle the new style messages.
3263 if ((recps != NULL) && (recps->num_ignet > 0))
3264 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3265 extract_token(recipient, recps->recp_ignet, i,
3266 '|', sizeof recipient);
3268 hold_R = msg->cm_fields['R'];
3269 hold_D = msg->cm_fields['D'];
3270 msg->cm_fields['R'] = malloc(SIZ);
3271 msg->cm_fields['D'] = malloc(128);
3272 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3273 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3275 serialize_message(&smr, msg);
3277 snprintf(submit_filename, sizeof submit_filename,
3278 "%s/netmail.%04lx.%04x.%04x",
3280 (long) getpid(), CCC->cs_pid, ++seqnum);
3281 network_fp = fopen(submit_filename, "wb+");
3282 if (network_fp != NULL) {
3283 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3289 free(msg->cm_fields['R']);
3290 free(msg->cm_fields['D']);
3291 msg->cm_fields['R'] = hold_R;
3292 msg->cm_fields['D'] = hold_D;
3295 /* Go back to the room we started from */
3296 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3297 if (strcasecmp(hold_rm, CCC->room.QRname))
3298 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3300 /* For internet mail, generate delivery instructions.
3301 * Yes, this is recursive. Deal with it. Infinite recursion does
3302 * not happen because the delivery instructions message does not
3303 * contain a recipient.
3305 if ((recps != NULL) && (recps->num_internet > 0)) {
3306 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3308 instr = malloc(instr_alloc);
3309 snprintf(instr, instr_alloc,
3310 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3312 SPOOLMIME, newmsgid, (long)time(NULL),
3316 if (recps->envelope_from != NULL) {
3317 tmp = strlen(instr);
3318 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3321 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3322 tmp = strlen(instr);
3323 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3324 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3325 instr_alloc = instr_alloc * 2;
3326 instr = realloc(instr, instr_alloc);
3328 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3331 imsg = malloc(sizeof(struct CtdlMessage));
3332 memset(imsg, 0, sizeof(struct CtdlMessage));
3333 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3334 imsg->cm_anon_type = MES_NORMAL;
3335 imsg->cm_format_type = FMT_RFC822;
3336 imsg->cm_fields['A'] = strdup("Citadel");
3337 imsg->cm_fields['J'] = strdup("do not journal");
3338 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3339 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3340 CtdlFreeMessage(imsg);
3344 * Any addresses to harvest for someone's address book?
3346 if ( (CCC->logged_in) && (recps != NULL) ) {
3347 collected_addresses = harvest_collected_addresses(msg);
3350 if (collected_addresses != NULL) {
3351 aptr = (struct addresses_to_be_filed *)
3352 malloc(sizeof(struct addresses_to_be_filed));
3353 CtdlMailboxName(actual_rm, sizeof actual_rm,
3354 &CCC->user, USERCONTACTSROOM);
3355 aptr->roomname = strdup(actual_rm);
3356 aptr->collected_addresses = collected_addresses;
3357 begin_critical_section(S_ATBF);
3360 end_critical_section(S_ATBF);
3364 * Determine whether this message qualifies for journaling.
3366 if (msg->cm_fields['J'] != NULL) {
3367 qualified_for_journaling = 0;
3370 if (recps == NULL) {
3371 qualified_for_journaling = config.c_journal_pubmsgs;
3373 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3374 qualified_for_journaling = config.c_journal_email;
3377 qualified_for_journaling = config.c_journal_pubmsgs;
3382 * Do we have to perform journaling? If so, hand off the saved
3383 * RFC822 version will be handed off to the journaler for background
3384 * submit. Otherwise, we have to free the memory ourselves.
3386 if (saved_rfc822_version != NULL) {
3387 if (qualified_for_journaling) {
3388 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3391 FreeStrBuf(&saved_rfc822_version);
3401 void aide_message (char *text, char *subject)
3403 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3408 * Convenience function for generating small administrative messages.
3410 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3411 int format_type, const char *subject)
3413 struct CtdlMessage *msg;
3414 struct recptypes *recp = NULL;
3416 msg = malloc(sizeof(struct CtdlMessage));
3417 memset(msg, 0, sizeof(struct CtdlMessage));
3418 msg->cm_magic = CTDLMESSAGE_MAGIC;
3419 msg->cm_anon_type = MES_NORMAL;
3420 msg->cm_format_type = format_type;
3423 msg->cm_fields['A'] = strdup(from);
3425 else if (fromaddr != NULL) {
3426 msg->cm_fields['A'] = strdup(fromaddr);
3427 if (strchr(msg->cm_fields['A'], '@')) {
3428 *strchr(msg->cm_fields['A'], '@') = 0;
3432 msg->cm_fields['A'] = strdup("Citadel");
3435 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3436 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3437 msg->cm_fields['N'] = strdup(NODENAME);
3439 msg->cm_fields['R'] = strdup(to);
3440 recp = validate_recipients(to, NULL, 0);
3442 if (subject != NULL) {
3443 msg->cm_fields['U'] = strdup(subject);
3445 msg->cm_fields['M'] = strdup(text);
3447 CtdlSubmitMsg(msg, recp, room, 0);
3448 CtdlFreeMessage(msg);
3449 if (recp != NULL) free_recipients(recp);
3455 * Back end function used by CtdlMakeMessage() and similar functions
3457 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3459 size_t maxlen, /* maximum message length */
3460 char *exist, /* if non-null, append to it;
3461 exist is ALWAYS freed */
3462 int crlf, /* CRLF newlines instead of LF */
3463 int *sock /* socket handle or 0 for this session's client socket */
3472 LineBuf = NewStrBufPlain(NULL, SIZ);
3473 if (exist == NULL) {
3474 Message = NewStrBufPlain(NULL, 4 * SIZ);
3477 Message = NewStrBufPlain(exist, -1);
3481 /* Do we need to change leading ".." to "." for SMTP escaping? */
3482 if ((tlen == 1) && (*terminator == '.')) {
3486 /* read in the lines of message text one by one */
3489 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3494 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3496 if ((StrLength(LineBuf) == tlen) &&
3497 (!strcmp(ChrPtr(LineBuf), terminator)))
3500 if ( (!flushing) && (!finished) ) {
3502 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3505 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3508 /* Unescape SMTP-style input of two dots at the beginning of the line */
3510 (StrLength(LineBuf) == 2) &&
3511 (!strcmp(ChrPtr(LineBuf), "..")))
3513 StrBufCutLeft(LineBuf, 1);
3516 StrBufAppendBuf(Message, LineBuf, 0);
3519 /* if we've hit the max msg length, flush the rest */
3520 if (StrLength(Message) >= maxlen) flushing = 1;
3522 } while (!finished);
3523 FreeStrBuf(&LineBuf);
3527 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3531 FreeStrBuf(&(*Msg)->MsgBuf);
3537 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3539 size_t maxlen, /* maximum message length */
3540 size_t expectlen, /* if we expect a message, how long should it be? */
3541 char *exist, /* if non-null, append to it;
3542 exist is ALWAYS freed */
3543 long eLen, /* length of exist */
3544 int crlf /* CRLF newlines instead of LF */
3547 ReadAsyncMsg *NewMsg;
3549 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3550 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3552 if (exist == NULL) {
3555 if (expectlen == 0) {
3559 len = expectlen + 10;
3561 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3564 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3567 /* Do we need to change leading ".." to "." for SMTP escaping? */
3568 if ((tlen == 1) && (*terminator == '.')) {
3572 NewMsg->terminator = terminator;
3573 NewMsg->tlen = tlen;
3575 NewMsg->maxlen = maxlen;
3577 NewMsg->crlf = crlf;
3583 * Back end function used by CtdlMakeMessage() and similar functions
3585 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3587 ReadAsyncMsg *ReadMsg;
3588 int MsgFinished = 0;
3589 eReadState Finished = eMustReadMore;
3594 const char *pch = ChrPtr(IO->SendBuf.Buf);
3595 const char *pchh = IO->SendBuf.ReadWritePointer;
3601 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3602 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3603 ((CitContext*)(IO->CitContext))->ServiceName,
3606 fd = fopen(fn, "a+");
3609 ReadMsg = IO->ReadMsg;
3611 /* read in the lines of message text one by one */
3613 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3616 case eMustReadMore: /// read new from socket...
3618 if (IO->RecvBuf.ReadWritePointer != NULL) {
3619 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3620 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3622 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3626 fprintf(fd, "BufferEmpty! \n");
3632 case eBufferNotEmpty: /* shouldn't happen... */
3633 case eReadSuccess: /// done for now...
3635 case eReadFail: /// WHUT?
3641 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3642 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3645 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3648 else if (!ReadMsg->flushing) {
3651 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3654 /* Unescape SMTP-style input of two dots at the beginning of the line */
3655 if ((ReadMsg->dodot) &&
3656 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3657 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3660 fprintf(fd, "UnEscaped!\n");
3662 StrBufCutLeft(IO->IOBuf, 1);
3665 if (ReadMsg->crlf) {
3666 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3669 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3672 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3675 /* if we've hit the max msg length, flush the rest */
3676 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3678 } while (!MsgFinished);
3681 fprintf(fd, "Done with reading; %s.\n, ",
3682 (MsgFinished)?"Message Finished": "FAILED");
3686 return eReadSuccess;
3693 * Back end function used by CtdlMakeMessage() and similar functions
3695 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3697 size_t maxlen, /* maximum message length */
3698 char *exist, /* if non-null, append to it;
3699 exist is ALWAYS freed */
3700 int crlf, /* CRLF newlines instead of LF */
3701 int *sock /* socket handle or 0 for this session's client socket */
3706 Message = CtdlReadMessageBodyBuf(terminator,
3712 if (Message == NULL)
3715 return SmashStrBuf(&Message);
3720 * Build a binary message to be saved on disk.
3721 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3722 * will become part of the message. This means you are no longer
3723 * responsible for managing that memory -- it will be freed along with
3724 * the rest of the fields when CtdlFreeMessage() is called.)
3727 struct CtdlMessage *CtdlMakeMessage(
3728 struct ctdluser *author, /* author's user structure */
3729 char *recipient, /* NULL if it's not mail */
3730 char *recp_cc, /* NULL if it's not mail */
3731 char *room, /* room where it's going */
3732 int type, /* see MES_ types in header file */
3733 int format_type, /* variformat, plain text, MIME... */
3734 char *fake_name, /* who we're masquerading as */
3735 char *my_email, /* which of my email addresses to use (empty is ok) */
3736 char *subject, /* Subject (optional) */
3737 char *supplied_euid, /* ...or NULL if this is irrelevant */
3738 char *preformatted_text, /* ...or NULL to read text from client */
3739 char *references /* Thread references */
3741 char dest_node[256];
3743 struct CtdlMessage *msg;
3745 StrBuf *FakeEncAuthor = NULL;
3747 msg = malloc(sizeof(struct CtdlMessage));
3748 memset(msg, 0, sizeof(struct CtdlMessage));
3749 msg->cm_magic = CTDLMESSAGE_MAGIC;
3750 msg->cm_anon_type = type;
3751 msg->cm_format_type = format_type;
3753 /* Don't confuse the poor folks if it's not routed mail. */
3754 strcpy(dest_node, "");
3756 if (recipient != NULL) striplt(recipient);
3757 if (recp_cc != NULL) striplt(recp_cc);
3759 /* Path or Return-Path */
3760 if (my_email == NULL) my_email = "";
3762 if (!IsEmptyStr(my_email)) {
3763 msg->cm_fields['P'] = strdup(my_email);
3766 snprintf(buf, sizeof buf, "%s", author->fullname);
3767 msg->cm_fields['P'] = strdup(buf);
3769 convert_spaces_to_underscores(msg->cm_fields['P']);
3771 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3772 msg->cm_fields['T'] = strdup(buf);
3774 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3775 FakeAuthor = NewStrBufPlain (fake_name, -1);
3778 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3780 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3781 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3782 FreeStrBuf(&FakeAuthor);
3784 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3785 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3788 msg->cm_fields['O'] = strdup(CC->room.QRname);
3791 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3792 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3794 if ((recipient != NULL) && (recipient[0] != 0)) {
3795 msg->cm_fields['R'] = strdup(recipient);
3797 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3798 msg->cm_fields['Y'] = strdup(recp_cc);
3800 if (dest_node[0] != 0) {
3801 msg->cm_fields['D'] = strdup(dest_node);
3804 if (!IsEmptyStr(my_email)) {
3805 msg->cm_fields['F'] = strdup(my_email);
3807 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3808 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3811 if (subject != NULL) {
3814 length = strlen(subject);
3820 while ((subject[i] != '\0') &&
3821 (IsAscii = isascii(subject[i]) != 0 ))
3824 msg->cm_fields['U'] = strdup(subject);
3825 else /* ok, we've got utf8 in the string. */
3827 msg->cm_fields['U'] = rfc2047encode(subject, length);
3833 if (supplied_euid != NULL) {
3834 msg->cm_fields['E'] = strdup(supplied_euid);
3837 if (references != NULL) {
3838 if (!IsEmptyStr(references)) {
3839 msg->cm_fields['W'] = strdup(references);
3843 if (preformatted_text != NULL) {
3844 msg->cm_fields['M'] = preformatted_text;
3847 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3855 * Check to see whether we have permission to post a message in the current
3856 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3857 * returns 0 on success.
3859 int CtdlDoIHavePermissionToPostInThisRoom(
3862 const char* RemoteIdentifier,
3868 if (!(CC->logged_in) &&
3869 (PostPublic == POST_LOGGED_IN)) {
3870 snprintf(errmsgbuf, n, "Not logged in.");
3871 return (ERROR + NOT_LOGGED_IN);
3873 else if (PostPublic == CHECK_EXISTANCE) {
3874 return (0); // We're Evaling whether a recipient exists
3876 else if (!(CC->logged_in)) {
3878 if ((CC->room.QRflags & QR_READONLY)) {
3879 snprintf(errmsgbuf, n, "Not logged in.");
3880 return (ERROR + NOT_LOGGED_IN);
3882 if (CC->room.QRflags2 & QR2_MODERATED) {
3883 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3884 return (ERROR + NOT_LOGGED_IN);
3886 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3891 if (RemoteIdentifier == NULL)
3893 snprintf(errmsgbuf, n, "Need sender to permit access.");
3894 return (ERROR + USERNAME_REQUIRED);
3897 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3898 begin_critical_section(S_NETCONFIGS);
3899 if (!read_spoolcontrol_file(&sc, filename))
3901 end_critical_section(S_NETCONFIGS);
3902 snprintf(errmsgbuf, n,
3903 "This mailing list only accepts posts from subscribers.");
3904 return (ERROR + NO_SUCH_USER);
3906 end_critical_section(S_NETCONFIGS);
3907 found = is_recipient (sc, RemoteIdentifier);
3908 free_spoolcontrol_struct(&sc);
3913 snprintf(errmsgbuf, n,
3914 "This mailing list only accepts posts from subscribers.");
3915 return (ERROR + NO_SUCH_USER);
3922 if ((CC->user.axlevel < AxProbU)
3923 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3924 snprintf(errmsgbuf, n, "Need to be validated to enter "
3925 "(except in %s> to sysop)", MAILROOM);
3926 return (ERROR + HIGHER_ACCESS_REQUIRED);
3929 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3931 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3933 * To be thorough, we ought to check to see if the message they are
3934 * replying to is actually a valid one in this room, but unless this
3935 * actually becomes a problem we'll go with high performance instead.
3937 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3938 return (ERROR + HIGHER_ACCESS_REQUIRED);
3941 else if (!(ra & UA_POSTALLOWED)) {
3942 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3943 return (ERROR + HIGHER_ACCESS_REQUIRED);
3946 strcpy(errmsgbuf, "Ok");
3952 * Check to see if the specified user has Internet mail permission
3953 * (returns nonzero if permission is granted)
3955 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3957 /* Do not allow twits to send Internet mail */
3958 if (who->axlevel <= AxProbU) return(0);
3960 /* Globally enabled? */
3961 if (config.c_restrict == 0) return(1);
3963 /* User flagged ok? */
3964 if (who->flags & US_INTERNET) return(2);
3966 /* Aide level access? */
3967 if (who->axlevel >= AxAideU) return(3);
3969 /* No mail for you! */
3975 * Validate recipients, count delivery types and errors, and handle aliasing
3976 * FIXME check for dupes!!!!!
3978 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3979 * were specified, or the number of addresses found invalid.
3981 * Caller needs to free the result using free_recipients()
3983 struct recptypes *validate_recipients(const char *supplied_recipients,
3984 const char *RemoteIdentifier,
3986 struct recptypes *ret;
3987 char *recipients = NULL;
3988 char this_recp[256];
3989 char this_recp_cooked[256];
3995 struct ctdluser tempUS;
3996 struct ctdlroom tempQR;
3997 struct ctdlroom tempQR2;
4003 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4004 if (ret == NULL) return(NULL);
4006 /* Set all strings to null and numeric values to zero */
4007 memset(ret, 0, sizeof(struct recptypes));
4009 if (supplied_recipients == NULL) {
4010 recipients = strdup("");
4013 recipients = strdup(supplied_recipients);
4016 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4017 * actually need, but it's healthier for the heap than doing lots of tiny
4018 * realloc() calls instead.
4021 ret->errormsg = malloc(strlen(recipients) + 1024);
4022 ret->recp_local = malloc(strlen(recipients) + 1024);
4023 ret->recp_internet = malloc(strlen(recipients) + 1024);
4024 ret->recp_ignet = malloc(strlen(recipients) + 1024);
4025 ret->recp_room = malloc(strlen(recipients) + 1024);
4026 ret->display_recp = malloc(strlen(recipients) + 1024);
4028 ret->errormsg[0] = 0;
4029 ret->recp_local[0] = 0;
4030 ret->recp_internet[0] = 0;
4031 ret->recp_ignet[0] = 0;
4032 ret->recp_room[0] = 0;
4033 ret->display_recp[0] = 0;
4035 ret->recptypes_magic = RECPTYPES_MAGIC;
4037 /* Change all valid separator characters to commas */
4038 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4039 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4040 recipients[i] = ',';
4044 /* Now start extracting recipients... */
4046 while (!IsEmptyStr(recipients)) {
4048 for (i=0; i<=strlen(recipients); ++i) {
4049 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4050 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4051 safestrncpy(this_recp, recipients, i+1);
4053 if (recipients[i] == ',') {
4054 strcpy(recipients, &recipients[i+1]);
4057 strcpy(recipients, "");
4064 if (IsEmptyStr(this_recp))
4066 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4068 mailtype = alias(this_recp);
4069 mailtype = alias(this_recp);
4070 mailtype = alias(this_recp);
4072 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4073 if (this_recp[j]=='_') {
4074 this_recp_cooked[j] = ' ';
4077 this_recp_cooked[j] = this_recp[j];
4080 this_recp_cooked[j] = '\0';
4085 if (!strcasecmp(this_recp, "sysop")) {
4087 strcpy(this_recp, config.c_aideroom);
4088 if (!IsEmptyStr(ret->recp_room)) {
4089 strcat(ret->recp_room, "|");
4091 strcat(ret->recp_room, this_recp);
4093 else if ( (!strncasecmp(this_recp, "room_", 5))
4094 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4096 /* Save room so we can restore it later */
4100 /* Check permissions to send mail to this room */
4101 err = CtdlDoIHavePermissionToPostInThisRoom(
4106 0 /* 0 = not a reply */
4115 if (!IsEmptyStr(ret->recp_room)) {
4116 strcat(ret->recp_room, "|");
4118 strcat(ret->recp_room, &this_recp_cooked[5]);
4121 /* Restore room in case something needs it */
4125 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4127 strcpy(this_recp, tempUS.fullname);
4128 if (!IsEmptyStr(ret->recp_local)) {
4129 strcat(ret->recp_local, "|");
4131 strcat(ret->recp_local, this_recp);
4133 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4135 strcpy(this_recp, tempUS.fullname);
4136 if (!IsEmptyStr(ret->recp_local)) {
4137 strcat(ret->recp_local, "|");
4139 strcat(ret->recp_local, this_recp);
4147 /* Yes, you're reading this correctly: if the target
4148 * domain points back to the local system or an attached
4149 * Citadel directory, the address is invalid. That's
4150 * because if the address were valid, we would have
4151 * already translated it to a local address by now.
4153 if (IsDirectory(this_recp, 0)) {
4158 ++ret->num_internet;
4159 if (!IsEmptyStr(ret->recp_internet)) {
4160 strcat(ret->recp_internet, "|");
4162 strcat(ret->recp_internet, this_recp);
4167 if (!IsEmptyStr(ret->recp_ignet)) {
4168 strcat(ret->recp_ignet, "|");
4170 strcat(ret->recp_ignet, this_recp);
4178 if (IsEmptyStr(errmsg)) {
4179 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4182 snprintf(append, sizeof append, "%s", errmsg);
4184 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4185 if (!IsEmptyStr(ret->errormsg)) {
4186 strcat(ret->errormsg, "; ");
4188 strcat(ret->errormsg, append);
4192 if (IsEmptyStr(ret->display_recp)) {
4193 strcpy(append, this_recp);
4196 snprintf(append, sizeof append, ", %s", this_recp);
4198 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4199 strcat(ret->display_recp, append);
4204 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4205 ret->num_room + ret->num_error) == 0) {
4206 ret->num_error = (-1);
4207 strcpy(ret->errormsg, "No recipients specified.");
4210 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
4211 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4212 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4213 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4214 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4215 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4223 * Destructor for struct recptypes
4225 void free_recipients(struct recptypes *valid) {
4227 if (valid == NULL) {
4231 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4232 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4236 if (valid->errormsg != NULL) free(valid->errormsg);
4237 if (valid->recp_local != NULL) free(valid->recp_local);
4238 if (valid->recp_internet != NULL) free(valid->recp_internet);
4239 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4240 if (valid->recp_room != NULL) free(valid->recp_room);
4241 if (valid->display_recp != NULL) free(valid->display_recp);
4242 if (valid->bounce_to != NULL) free(valid->bounce_to);
4243 if (valid->envelope_from != NULL) free(valid->envelope_from);
4250 * message entry - mode 0 (normal)
4252 void cmd_ent0(char *entargs)
4258 char supplied_euid[128];
4260 int format_type = 0;
4261 char newusername[256];
4262 char newuseremail[256];
4263 struct CtdlMessage *msg;
4267 struct recptypes *valid = NULL;
4268 struct recptypes *valid_to = NULL;
4269 struct recptypes *valid_cc = NULL;
4270 struct recptypes *valid_bcc = NULL;
4272 int subject_required = 0;
4277 int newuseremail_ok = 0;
4278 char references[SIZ];
4283 post = extract_int(entargs, 0);
4284 extract_token(recp, entargs, 1, '|', sizeof recp);
4285 anon_flag = extract_int(entargs, 2);
4286 format_type = extract_int(entargs, 3);
4287 extract_token(subject, entargs, 4, '|', sizeof subject);
4288 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4289 do_confirm = extract_int(entargs, 6);
4290 extract_token(cc, entargs, 7, '|', sizeof cc);
4291 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4292 switch(CC->room.QRdefaultview) {
4295 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4298 supplied_euid[0] = 0;
4301 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4302 extract_token(references, entargs, 11, '|', sizeof references);
4303 for (ptr=references; *ptr != 0; ++ptr) {
4304 if (*ptr == '!') *ptr = '|';
4307 /* first check to make sure the request is valid. */
4309 err = CtdlDoIHavePermissionToPostInThisRoom(
4314 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4318 cprintf("%d %s\n", err, errmsg);
4322 /* Check some other permission type things. */
4324 if (IsEmptyStr(newusername)) {
4325 strcpy(newusername, CC->user.fullname);
4327 if ( (CC->user.axlevel < AxAideU)
4328 && (strcasecmp(newusername, CC->user.fullname))
4329 && (strcasecmp(newusername, CC->cs_inet_fn))
4331 cprintf("%d You don't have permission to author messages as '%s'.\n",
4332 ERROR + HIGHER_ACCESS_REQUIRED,
4339 if (IsEmptyStr(newuseremail)) {
4340 newuseremail_ok = 1;
4343 if (!IsEmptyStr(newuseremail)) {
4344 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4345 newuseremail_ok = 1;
4347 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4348 j = num_tokens(CC->cs_inet_other_emails, '|');
4349 for (i=0; i<j; ++i) {
4350 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4351 if (!strcasecmp(newuseremail, buf)) {
4352 newuseremail_ok = 1;
4358 if (!newuseremail_ok) {
4359 cprintf("%d You don't have permission to author messages as '%s'.\n",
4360 ERROR + HIGHER_ACCESS_REQUIRED,
4366 CC->cs_flags |= CS_POSTING;
4368 /* In mailbox rooms we have to behave a little differently --
4369 * make sure the user has specified at least one recipient. Then
4370 * validate the recipient(s). We do this for the Mail> room, as
4371 * well as any room which has the "Mailbox" view set - unless it
4372 * is the DRAFTS room which does not require recipients
4375 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4376 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4377 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4378 if (CC->user.axlevel < AxProbU) {
4379 strcpy(recp, "sysop");
4384 valid_to = validate_recipients(recp, NULL, 0);
4385 if (valid_to->num_error > 0) {
4386 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4387 free_recipients(valid_to);
4391 valid_cc = validate_recipients(cc, NULL, 0);
4392 if (valid_cc->num_error > 0) {
4393 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4394 free_recipients(valid_to);
4395 free_recipients(valid_cc);
4399 valid_bcc = validate_recipients(bcc, NULL, 0);
4400 if (valid_bcc->num_error > 0) {
4401 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4402 free_recipients(valid_to);
4403 free_recipients(valid_cc);
4404 free_recipients(valid_bcc);
4408 /* Recipient required, but none were specified */
4409 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4410 free_recipients(valid_to);
4411 free_recipients(valid_cc);
4412 free_recipients(valid_bcc);
4413 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4417 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4418 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4419 cprintf("%d You do not have permission "
4420 "to send Internet mail.\n",
4421 ERROR + HIGHER_ACCESS_REQUIRED);
4422 free_recipients(valid_to);
4423 free_recipients(valid_cc);
4424 free_recipients(valid_bcc);
4429 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)
4430 && (CC->user.axlevel < AxNetU) ) {
4431 cprintf("%d Higher access required for network mail.\n",
4432 ERROR + HIGHER_ACCESS_REQUIRED);
4433 free_recipients(valid_to);
4434 free_recipients(valid_cc);
4435 free_recipients(valid_bcc);
4439 if ((RESTRICT_INTERNET == 1)
4440 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4441 && ((CC->user.flags & US_INTERNET) == 0)
4442 && (!CC->internal_pgm)) {
4443 cprintf("%d You don't have access to Internet mail.\n",
4444 ERROR + HIGHER_ACCESS_REQUIRED);
4445 free_recipients(valid_to);
4446 free_recipients(valid_cc);
4447 free_recipients(valid_bcc);
4453 /* Is this a room which has anonymous-only or anonymous-option? */
4454 anonymous = MES_NORMAL;
4455 if (CC->room.QRflags & QR_ANONONLY) {
4456 anonymous = MES_ANONONLY;
4458 if (CC->room.QRflags & QR_ANONOPT) {
4459 if (anon_flag == 1) { /* only if the user requested it */
4460 anonymous = MES_ANONOPT;
4464 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4468 /* Recommend to the client that the use of a message subject is
4469 * strongly recommended in this room, if either the SUBJECTREQ flag
4470 * is set, or if there is one or more Internet email recipients.
4472 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4473 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4474 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4475 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4477 /* If we're only checking the validity of the request, return
4478 * success without creating the message.
4481 cprintf("%d %s|%d\n", CIT_OK,
4482 ((valid_to != NULL) ? valid_to->display_recp : ""),
4484 free_recipients(valid_to);
4485 free_recipients(valid_cc);
4486 free_recipients(valid_bcc);
4490 /* We don't need these anymore because we'll do it differently below */
4491 free_recipients(valid_to);
4492 free_recipients(valid_cc);
4493 free_recipients(valid_bcc);
4495 /* Read in the message from the client. */
4497 cprintf("%d send message\n", START_CHAT_MODE);
4499 cprintf("%d send message\n", SEND_LISTING);
4502 msg = CtdlMakeMessage(&CC->user, recp, cc,
4503 CC->room.QRname, anonymous, format_type,
4504 newusername, newuseremail, subject,
4505 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4508 /* Put together one big recipients struct containing to/cc/bcc all in
4509 * one. This is for the envelope.
4511 char *all_recps = malloc(SIZ * 3);
4512 strcpy(all_recps, recp);
4513 if (!IsEmptyStr(cc)) {
4514 if (!IsEmptyStr(all_recps)) {
4515 strcat(all_recps, ",");
4517 strcat(all_recps, cc);
4519 if (!IsEmptyStr(bcc)) {
4520 if (!IsEmptyStr(all_recps)) {
4521 strcat(all_recps, ",");
4523 strcat(all_recps, bcc);
4525 if (!IsEmptyStr(all_recps)) {
4526 valid = validate_recipients(all_recps, NULL, 0);
4534 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4537 cprintf("%ld\n", msgnum);
4539 cprintf("Message accepted.\n");
4542 cprintf("Internal error.\n");
4544 if (msg->cm_fields['E'] != NULL) {
4545 cprintf("%s\n", msg->cm_fields['E']);
4552 CtdlFreeMessage(msg);
4554 if (valid != NULL) {
4555 free_recipients(valid);
4563 * API function to delete messages which match a set of criteria
4564 * (returns the actual number of messages deleted)
4566 int CtdlDeleteMessages(char *room_name, /* which room */
4567 long *dmsgnums, /* array of msg numbers to be deleted */
4568 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4569 char *content_type /* or "" for any. regular expressions expected. */
4572 struct ctdlroom qrbuf;
4573 struct cdbdata *cdbfr;
4574 long *msglist = NULL;
4575 long *dellist = NULL;
4578 int num_deleted = 0;
4580 struct MetaData smi;
4583 int need_to_free_re = 0;
4585 if (content_type) if (!IsEmptyStr(content_type)) {
4586 regcomp(&re, content_type, 0);
4587 need_to_free_re = 1;
4589 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4590 room_name, num_dmsgnums, content_type);
4592 /* get room record, obtaining a lock... */
4593 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4594 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4596 if (need_to_free_re) regfree(&re);
4597 return (0); /* room not found */
4599 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4601 if (cdbfr != NULL) {
4602 dellist = malloc(cdbfr->len);
4603 msglist = (long *) cdbfr->ptr;
4604 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4605 num_msgs = cdbfr->len / sizeof(long);
4609 for (i = 0; i < num_msgs; ++i) {
4612 /* Set/clear a bit for each criterion */
4614 /* 0 messages in the list or a null list means that we are
4615 * interested in deleting any messages which meet the other criteria.
4617 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4618 delete_this |= 0x01;
4621 for (j=0; j<num_dmsgnums; ++j) {
4622 if (msglist[i] == dmsgnums[j]) {
4623 delete_this |= 0x01;
4628 if (IsEmptyStr(content_type)) {
4629 delete_this |= 0x02;
4631 GetMetaData(&smi, msglist[i]);
4632 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4633 delete_this |= 0x02;
4637 /* Delete message only if all bits are set */
4638 if (delete_this == 0x03) {
4639 dellist[num_deleted++] = msglist[i];
4644 num_msgs = sort_msglist(msglist, num_msgs);
4645 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4646 msglist, (int)(num_msgs * sizeof(long)));
4649 qrbuf.QRhighest = msglist[num_msgs - 1];
4651 qrbuf.QRhighest = 0;
4653 CtdlPutRoomLock(&qrbuf);
4655 /* Go through the messages we pulled out of the index, and decrement
4656 * their reference counts by 1. If this is the only room the message
4657 * was in, the reference count will reach zero and the message will
4658 * automatically be deleted from the database. We do this in a
4659 * separate pass because there might be plug-in hooks getting called,
4660 * and we don't want that happening during an S_ROOMS critical
4663 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4664 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4665 AdjRefCount(dellist[i], -1);
4668 /* Now free the memory we used, and go away. */
4669 if (msglist != NULL) free(msglist);
4670 if (dellist != NULL) free(dellist);
4671 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4672 if (need_to_free_re) regfree(&re);
4673 return (num_deleted);
4679 * Check whether the current user has permission to delete messages from
4680 * the current room (returns 1 for yes, 0 for no)
4682 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4684 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4685 if (ra & UA_DELETEALLOWED) return(1);
4693 * Delete message from current room
4695 void cmd_dele(char *args)
4704 extract_token(msgset, args, 0, '|', sizeof msgset);
4705 num_msgs = num_tokens(msgset, ',');
4707 cprintf("%d Nothing to do.\n", CIT_OK);
4711 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4712 cprintf("%d Higher access required.\n",
4713 ERROR + HIGHER_ACCESS_REQUIRED);
4718 * Build our message set to be moved/copied
4720 msgs = malloc(num_msgs * sizeof(long));
4721 for (i=0; i<num_msgs; ++i) {
4722 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4723 msgs[i] = atol(msgtok);
4726 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4730 cprintf("%d %d message%s deleted.\n", CIT_OK,
4731 num_deleted, ((num_deleted != 1) ? "s" : ""));
4733 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4741 * move or copy a message to another room
4743 void cmd_move(char *args)
4750 char targ[ROOMNAMELEN];
4751 struct ctdlroom qtemp;
4758 extract_token(msgset, args, 0, '|', sizeof msgset);
4759 num_msgs = num_tokens(msgset, ',');
4761 cprintf("%d Nothing to do.\n", CIT_OK);
4765 extract_token(targ, args, 1, '|', sizeof targ);
4766 convert_room_name_macros(targ, sizeof targ);
4767 targ[ROOMNAMELEN - 1] = 0;
4768 is_copy = extract_int(args, 2);
4770 if (CtdlGetRoom(&qtemp, targ) != 0) {
4771 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4775 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4776 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4780 CtdlGetUser(&CC->user, CC->curr_user);
4781 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4783 /* Check for permission to perform this operation.
4784 * Remember: "CC->room" is source, "qtemp" is target.
4788 /* Aides can move/copy */
4789 if (CC->user.axlevel >= AxAideU) permit = 1;
4791 /* Room aides can move/copy */
4792 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4794 /* Permit move/copy from personal rooms */
4795 if ((CC->room.QRflags & QR_MAILBOX)
4796 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4798 /* Permit only copy from public to personal room */
4800 && (!(CC->room.QRflags & QR_MAILBOX))
4801 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4803 /* Permit message removal from collaborative delete rooms */
4804 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4806 /* Users allowed to post into the target room may move into it too. */
4807 if ((CC->room.QRflags & QR_MAILBOX) &&
4808 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4810 /* User must have access to target room */
4811 if (!(ra & UA_KNOWN)) permit = 0;
4814 cprintf("%d Higher access required.\n",
4815 ERROR + HIGHER_ACCESS_REQUIRED);
4820 * Build our message set to be moved/copied
4822 msgs = malloc(num_msgs * sizeof(long));
4823 for (i=0; i<num_msgs; ++i) {
4824 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4825 msgs[i] = atol(msgtok);
4831 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4833 cprintf("%d Cannot store message(s) in %s: error %d\n",
4839 /* Now delete the message from the source room,
4840 * if this is a 'move' rather than a 'copy' operation.
4843 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4847 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4853 * GetMetaData() - Get the supplementary record for a message
4855 void GetMetaData(struct MetaData *smibuf, long msgnum)
4858 struct cdbdata *cdbsmi;
4861 memset(smibuf, 0, sizeof(struct MetaData));
4862 smibuf->meta_msgnum = msgnum;
4863 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4865 /* Use the negative of the message number for its supp record index */
4866 TheIndex = (0L - msgnum);
4868 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4869 if (cdbsmi == NULL) {
4870 return; /* record not found; go with defaults */
4872 memcpy(smibuf, cdbsmi->ptr,
4873 ((cdbsmi->len > sizeof(struct MetaData)) ?
4874 sizeof(struct MetaData) : cdbsmi->len));
4881 * PutMetaData() - (re)write supplementary record for a message
4883 void PutMetaData(struct MetaData *smibuf)
4887 /* Use the negative of the message number for the metadata db index */
4888 TheIndex = (0L - smibuf->meta_msgnum);
4890 cdb_store(CDB_MSGMAIN,
4891 &TheIndex, (int)sizeof(long),
4892 smibuf, (int)sizeof(struct MetaData));
4897 * AdjRefCount - submit an adjustment to the reference count for a message.
4898 * (These are just queued -- we actually process them later.)
4900 void AdjRefCount(long msgnum, int incr)
4902 struct arcq new_arcq;
4905 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4909 begin_critical_section(S_SUPPMSGMAIN);
4910 if (arcfp == NULL) {
4911 arcfp = fopen(file_arcq, "ab+");
4913 end_critical_section(S_SUPPMSGMAIN);
4915 /* msgnum < 0 means that we're trying to close the file */
4917 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4918 begin_critical_section(S_SUPPMSGMAIN);
4919 if (arcfp != NULL) {
4923 end_critical_section(S_SUPPMSGMAIN);
4928 * If we can't open the queue, perform the operation synchronously.
4930 if (arcfp == NULL) {
4931 TDAP_AdjRefCount(msgnum, incr);
4935 new_arcq.arcq_msgnum = msgnum;
4936 new_arcq.arcq_delta = incr;
4937 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4945 * TDAP_ProcessAdjRefCountQueue()
4947 * Process the queue of message count adjustments that was created by calls
4948 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4949 * for each one. This should be an "off hours" operation.
4951 int TDAP_ProcessAdjRefCountQueue(void)
4953 char file_arcq_temp[PATH_MAX];
4956 struct arcq arcq_rec;
4957 int num_records_processed = 0;
4959 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4961 begin_critical_section(S_SUPPMSGMAIN);
4962 if (arcfp != NULL) {
4967 r = link(file_arcq, file_arcq_temp);
4969 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4970 end_critical_section(S_SUPPMSGMAIN);
4971 return(num_records_processed);
4975 end_critical_section(S_SUPPMSGMAIN);
4977 fp = fopen(file_arcq_temp, "rb");
4979 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4980 return(num_records_processed);
4983 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4984 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4985 ++num_records_processed;
4989 r = unlink(file_arcq_temp);
4991 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4994 return(num_records_processed);
5000 * TDAP_AdjRefCount - adjust the reference count for a message.
5001 * This one does it "for real" because it's called by
5002 * the autopurger function that processes the queue
5003 * created by AdjRefCount(). If a message's reference
5004 * count becomes zero, we also delete the message from
5005 * disk and de-index it.
5007 void TDAP_AdjRefCount(long msgnum, int incr)
5010 struct MetaData smi;
5013 /* This is a *tight* critical section; please keep it that way, as
5014 * it may get called while nested in other critical sections.
5015 * Complicating this any further will surely cause deadlock!
5017 begin_critical_section(S_SUPPMSGMAIN);
5018 GetMetaData(&smi, msgnum);
5019 smi.meta_refcount += incr;
5021 end_critical_section(S_SUPPMSGMAIN);
5022 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5023 msgnum, incr, smi.meta_refcount
5026 /* If the reference count is now zero, delete the message
5027 * (and its supplementary record as well).
5029 if (smi.meta_refcount == 0) {
5030 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
5032 /* Call delete hooks with NULL room to show it has gone altogether */
5033 PerformDeleteHooks(NULL, msgnum);
5035 /* Remove from message base */
5037 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5038 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5040 /* Remove metadata record */
5041 delnum = (0L - msgnum);
5042 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5048 * Write a generic object to this room
5050 * Note: this could be much more efficient. Right now we use two temporary
5051 * files, and still pull the message into memory as with all others.
5053 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5054 char *content_type, /* MIME type of this object */
5055 char *raw_message, /* Data to be written */
5056 off_t raw_length, /* Size of raw_message */
5057 struct ctdluser *is_mailbox, /* Mailbox room? */
5058 int is_binary, /* Is encoding necessary? */
5059 int is_unique, /* Del others of this type? */
5060 unsigned int flags /* Internal save flags */
5064 struct ctdlroom qrbuf;
5065 char roomname[ROOMNAMELEN];
5066 struct CtdlMessage *msg;
5067 char *encoded_message = NULL;
5069 if (is_mailbox != NULL) {
5070 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5073 safestrncpy(roomname, req_room, sizeof(roomname));
5076 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
5079 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5082 encoded_message = malloc((size_t)(raw_length + 4096));
5085 sprintf(encoded_message, "Content-type: %s\n", content_type);
5088 sprintf(&encoded_message[strlen(encoded_message)],
5089 "Content-transfer-encoding: base64\n\n"
5093 sprintf(&encoded_message[strlen(encoded_message)],
5094 "Content-transfer-encoding: 7bit\n\n"
5100 &encoded_message[strlen(encoded_message)],
5108 &encoded_message[strlen(encoded_message)],
5114 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
5115 msg = malloc(sizeof(struct CtdlMessage));
5116 memset(msg, 0, sizeof(struct CtdlMessage));
5117 msg->cm_magic = CTDLMESSAGE_MAGIC;
5118 msg->cm_anon_type = MES_NORMAL;
5119 msg->cm_format_type = 4;
5120 msg->cm_fields['A'] = strdup(CC->user.fullname);
5121 msg->cm_fields['O'] = strdup(req_room);
5122 msg->cm_fields['N'] = strdup(config.c_nodename);
5123 msg->cm_fields['H'] = strdup(config.c_humannode);
5124 msg->cm_flags = flags;
5126 msg->cm_fields['M'] = encoded_message;
5128 /* Create the requested room if we have to. */
5129 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5130 CtdlCreateRoom(roomname,
5131 ( (is_mailbox != NULL) ? 5 : 3 ),
5132 "", 0, 1, 0, VIEW_BBS);
5134 /* If the caller specified this object as unique, delete all
5135 * other objects of this type that are currently in the room.
5138 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
5139 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5142 /* Now write the data */
5143 CtdlSubmitMsg(msg, NULL, roomname, 0);
5144 CtdlFreeMessage(msg);
5152 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5153 config_msgnum = msgnum;
5157 char *CtdlGetSysConfig(char *sysconfname) {
5158 char hold_rm[ROOMNAMELEN];
5161 struct CtdlMessage *msg;
5164 strcpy(hold_rm, CC->room.QRname);
5165 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5166 CtdlGetRoom(&CC->room, hold_rm);
5171 /* We want the last (and probably only) config in this room */
5172 begin_critical_section(S_CONFIG);
5173 config_msgnum = (-1L);
5174 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5175 CtdlGetSysConfigBackend, NULL);
5176 msgnum = config_msgnum;
5177 end_critical_section(S_CONFIG);
5183 msg = CtdlFetchMessage(msgnum, 1);
5185 conf = strdup(msg->cm_fields['M']);
5186 CtdlFreeMessage(msg);
5193 CtdlGetRoom(&CC->room, hold_rm);
5195 if (conf != NULL) do {
5196 extract_token(buf, conf, 0, '\n', sizeof buf);
5197 strcpy(conf, &conf[strlen(buf)+1]);
5198 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5204 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5205 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5210 * Determine whether a given Internet address belongs to the current user
5212 int CtdlIsMe(char *addr, int addr_buf_len)
5214 struct recptypes *recp;
5217 recp = validate_recipients(addr, NULL, 0);
5218 if (recp == NULL) return(0);
5220 if (recp->num_local == 0) {
5221 free_recipients(recp);
5225 for (i=0; i<recp->num_local; ++i) {
5226 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5227 if (!strcasecmp(addr, CC->user.fullname)) {
5228 free_recipients(recp);
5233 free_recipients(recp);
5239 * Citadel protocol command to do the same
5241 void cmd_isme(char *argbuf) {
5244 if (CtdlAccessCheck(ac_logged_in)) return;
5245 extract_token(addr, argbuf, 0, '|', sizeof addr);
5247 if (CtdlIsMe(addr, sizeof addr)) {
5248 cprintf("%d %s\n", CIT_OK, addr);
5251 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5257 /*****************************************************************************/
5258 /* MODULE INITIALIZATION STUFF */
5259 /*****************************************************************************/
5261 CTDL_MODULE_INIT(msgbase)
5264 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5265 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5266 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5267 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5268 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5269 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5270 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5271 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5272 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5273 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5274 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5275 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5278 /* return our Subversion id for the Log */