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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_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 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
947 if (client_write(nl, nllen) == -1)
949 syslog(LOG_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 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
965 if (client_write(nl, nllen) == -1)
967 syslog(LOG_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 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
987 if (client_write(nl, nllen) == -1)
989 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
997 if (client_write(outbuf, len) == -1)
999 syslog(LOG_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);
1130 * Load a message from disk into memory.
1131 * This is used by CtdlOutputMsg() and other fetch functions.
1133 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1134 * using the CtdlMessageFree() function.
1136 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1138 struct cdbdata *dmsgtext;
1139 struct CtdlMessage *ret = NULL;
1143 cit_uint8_t field_header;
1145 syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1146 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1147 if (dmsgtext == NULL) {
1150 mptr = dmsgtext->ptr;
1151 upper_bound = mptr + dmsgtext->len;
1153 /* Parse the three bytes that begin EVERY message on disk.
1154 * The first is always 0xFF, the on-disk magic number.
1155 * The second is the anonymous/public type byte.
1156 * The third is the format type byte (vari, fixed, or MIME).
1160 syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1164 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1165 memset(ret, 0, sizeof(struct CtdlMessage));
1167 ret->cm_magic = CTDLMESSAGE_MAGIC;
1168 ret->cm_anon_type = *mptr++; /* Anon type byte */
1169 ret->cm_format_type = *mptr++; /* Format type byte */
1172 * The rest is zero or more arbitrary fields. Load them in.
1173 * We're done when we encounter either a zero-length field or
1174 * have just processed the 'M' (message text) field.
1177 if (mptr >= upper_bound) {
1180 field_header = *mptr++;
1181 ret->cm_fields[field_header] = strdup(mptr);
1183 while (*mptr++ != 0); /* advance to next field */
1185 } while ((mptr < upper_bound) && (field_header != 'M'));
1189 /* Always make sure there's something in the msg text field. If
1190 * it's NULL, the message text is most likely stored separately,
1191 * so go ahead and fetch that. Failing that, just set a dummy
1192 * body so other code doesn't barf.
1194 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1195 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1196 if (dmsgtext != NULL) {
1197 ret->cm_fields['M'] = dmsgtext->ptr;
1198 dmsgtext->ptr = NULL;
1202 if (ret->cm_fields['M'] == NULL) {
1203 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1206 /* Perform "before read" hooks (aborting if any return nonzero) */
1207 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1208 CtdlFreeMessage(ret);
1217 * Returns 1 if the supplied pointer points to a valid Citadel message.
1218 * If the pointer is NULL or the magic number check fails, returns 0.
1220 int is_valid_message(struct CtdlMessage *msg) {
1223 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1224 syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1232 * 'Destructor' for struct CtdlMessage
1234 void CtdlFreeMessage(struct CtdlMessage *msg)
1238 if (is_valid_message(msg) == 0)
1240 if (msg != NULL) free (msg);
1244 for (i = 0; i < 256; ++i)
1245 if (msg->cm_fields[i] != NULL) {
1246 free(msg->cm_fields[i]);
1249 msg->cm_magic = 0; /* just in case */
1255 * Pre callback function for multipart/alternative
1257 * NOTE: this differs from the standard behavior for a reason. Normally when
1258 * displaying multipart/alternative you want to show the _last_ usable
1259 * format in the message. Here we show the _first_ one, because it's
1260 * usually text/plain. Since this set of functions is designed for text
1261 * output to non-MIME-aware clients, this is the desired behavior.
1264 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1265 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1266 char *cbid, void *cbuserdata)
1270 ma = (struct ma_info *)cbuserdata;
1271 syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1272 if (!strcasecmp(cbtype, "multipart/alternative")) {
1276 if (!strcasecmp(cbtype, "message/rfc822")) {
1282 * Post callback function for multipart/alternative
1284 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1285 void *content, char *cbtype, char *cbcharset, size_t length,
1286 char *encoding, char *cbid, void *cbuserdata)
1290 ma = (struct ma_info *)cbuserdata;
1291 syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1292 if (!strcasecmp(cbtype, "multipart/alternative")) {
1296 if (!strcasecmp(cbtype, "message/rfc822")) {
1302 * Inline callback function for mime parser that wants to display text
1304 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1305 void *content, char *cbtype, char *cbcharset, size_t length,
1306 char *encoding, char *cbid, void *cbuserdata)
1313 ma = (struct ma_info *)cbuserdata;
1316 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1317 partnum, filename, cbtype, (long)length);
1320 * If we're in the middle of a multipart/alternative scope and
1321 * we've already printed another section, skip this one.
1323 if ( (ma->is_ma) && (ma->did_print) ) {
1324 syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1329 if ( (!strcasecmp(cbtype, "text/plain"))
1330 || (IsEmptyStr(cbtype)) ) {
1333 client_write(wptr, length);
1334 if (wptr[length-1] != '\n') {
1341 if (!strcasecmp(cbtype, "text/html")) {
1342 ptr = html_to_ascii(content, length, 80, 0);
1344 client_write(ptr, wlen);
1345 if (ptr[wlen-1] != '\n') {
1352 if (ma->use_fo_hooks) {
1353 if (PerformFixedOutputHooks(cbtype, content, length)) {
1354 /* above function returns nonzero if it handled the part */
1359 if (strncasecmp(cbtype, "multipart/", 10)) {
1360 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1361 partnum, filename, cbtype, (long)length);
1367 * The client is elegant and sophisticated and wants to be choosy about
1368 * MIME content types, so figure out which multipart/alternative part
1369 * we're going to send.
1371 * We use a system of weights. When we find a part that matches one of the
1372 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1373 * and then set ma->chosen_pref to that MIME type's position in our preference
1374 * list. If we then hit another match, we only replace the first match if
1375 * the preference value is lower.
1377 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1378 void *content, char *cbtype, char *cbcharset, size_t length,
1379 char *encoding, char *cbid, void *cbuserdata)
1385 ma = (struct ma_info *)cbuserdata;
1387 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1388 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1389 // I don't know if there are any side effects! Please TEST TEST TEST
1390 //if (ma->is_ma > 0) {
1392 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1393 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1394 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1395 if (i < ma->chosen_pref) {
1396 syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1397 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1398 ma->chosen_pref = i;
1405 * Now that we've chosen our preferred part, output it.
1407 void output_preferred(char *name,
1421 int add_newline = 0;
1424 char *decoded = NULL;
1425 size_t bytes_decoded;
1428 ma = (struct ma_info *)cbuserdata;
1430 /* This is not the MIME part you're looking for... */
1431 if (strcasecmp(partnum, ma->chosen_part)) return;
1433 /* If the content-type of this part is in our preferred formats
1434 * list, we can simply output it verbatim.
1436 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1437 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1438 if (!strcasecmp(buf, cbtype)) {
1439 /* Yeah! Go! W00t!! */
1440 if (ma->dont_decode == 0)
1441 rc = mime_decode_now (content,
1447 break; /* Give us the chance, maybe theres another one. */
1449 if (rc == 0) text_content = (char *)content;
1451 text_content = decoded;
1452 length = bytes_decoded;
1455 if (text_content[length-1] != '\n') {
1458 cprintf("Content-type: %s", cbtype);
1459 if (!IsEmptyStr(cbcharset)) {
1460 cprintf("; charset=%s", cbcharset);
1462 cprintf("\nContent-length: %d\n",
1463 (int)(length + add_newline) );
1464 if (!IsEmptyStr(encoding)) {
1465 cprintf("Content-transfer-encoding: %s\n", encoding);
1468 cprintf("Content-transfer-encoding: 7bit\n");
1470 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1472 if (client_write(text_content, length) == -1)
1474 syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1477 if (add_newline) cprintf("\n");
1478 if (decoded != NULL) free(decoded);
1483 /* No translations required or possible: output as text/plain */
1484 cprintf("Content-type: text/plain\n\n");
1486 if (ma->dont_decode == 0)
1487 rc = mime_decode_now (content,
1493 return; /* Give us the chance, maybe theres another one. */
1495 if (rc == 0) text_content = (char *)content;
1497 text_content = decoded;
1498 length = bytes_decoded;
1501 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1502 length, encoding, cbid, cbuserdata);
1503 if (decoded != NULL) free(decoded);
1508 char desired_section[64];
1515 * Callback function for
1517 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1518 void *content, char *cbtype, char *cbcharset, size_t length,
1519 char *encoding, char *cbid, void *cbuserdata)
1521 struct encapmsg *encap;
1523 encap = (struct encapmsg *)cbuserdata;
1525 /* Only proceed if this is the desired section... */
1526 if (!strcasecmp(encap->desired_section, partnum)) {
1527 encap->msglen = length;
1528 encap->msg = malloc(length + 2);
1529 memcpy(encap->msg, content, length);
1536 * Determine whether the currently logged in session has permission to read
1537 * messages in the current room.
1539 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1540 if ( (!(CC->logged_in))
1541 && (!(CC->internal_pgm))
1542 && (!config.c_guest_logins)
1544 return(om_not_logged_in);
1551 * Get a message off disk. (returns om_* values found in msgbase.h)
1554 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1555 int mode, /* how would you like that message? */
1556 int headers_only, /* eschew the message body? */
1557 int do_proto, /* do Citadel protocol responses? */
1558 int crlf, /* Use CRLF newlines instead of LF? */
1559 char *section, /* NULL or a message/rfc822 section */
1560 int flags /* various flags; see msgbase.h */
1562 struct CtdlMessage *TheMessage = NULL;
1563 int retcode = om_no_such_msg;
1564 struct encapmsg encap;
1567 syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1569 (section ? section : "<>")
1572 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1575 if (r == om_not_logged_in) {
1576 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1579 cprintf("%d An unknown error has occurred.\n", ERROR);
1586 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1587 * request that we don't even bother loading the body into memory.
1589 if (headers_only == HEADERS_FAST) {
1590 TheMessage = CtdlFetchMessage(msg_num, 0);
1593 TheMessage = CtdlFetchMessage(msg_num, 1);
1596 if (TheMessage == NULL) {
1597 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1598 ERROR + MESSAGE_NOT_FOUND, msg_num);
1599 return(om_no_such_msg);
1602 /* Here is the weird form of this command, to process only an
1603 * encapsulated message/rfc822 section.
1605 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1606 memset(&encap, 0, sizeof encap);
1607 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1608 mime_parser(TheMessage->cm_fields['M'],
1610 *extract_encapsulated_message,
1611 NULL, NULL, (void *)&encap, 0
1613 CtdlFreeMessage(TheMessage);
1617 encap.msg[encap.msglen] = 0;
1618 TheMessage = convert_internet_message(encap.msg);
1619 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1621 /* Now we let it fall through to the bottom of this
1622 * function, because TheMessage now contains the
1623 * encapsulated message instead of the top-level
1624 * message. Isn't that neat?
1629 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1630 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1631 retcode = om_no_such_msg;
1636 /* Ok, output the message now */
1637 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1638 CtdlFreeMessage(TheMessage);
1644 char *qp_encode_email_addrs(char *source)
1646 char *user, *node, *name;
1647 const char headerStr[] = "=?UTF-8?Q?";
1651 int need_to_encode = 0;
1657 long nAddrPtrMax = 50;
1662 if (source == NULL) return source;
1663 if (IsEmptyStr(source)) return source;
1665 syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1667 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1668 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1669 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1672 while (!IsEmptyStr (&source[i])) {
1673 if (nColons >= nAddrPtrMax){
1676 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1677 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1678 free (AddrPtr), AddrPtr = ptr;
1680 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1681 memset(&ptr[nAddrPtrMax], 0,
1682 sizeof (long) * nAddrPtrMax);
1684 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1685 free (AddrUtf8), AddrUtf8 = ptr;
1688 if (((unsigned char) source[i] < 32) ||
1689 ((unsigned char) source[i] > 126)) {
1691 AddrUtf8[nColons] = 1;
1693 if (source[i] == '"')
1694 InQuotes = !InQuotes;
1695 if (!InQuotes && source[i] == ',') {
1696 AddrPtr[nColons] = i;
1701 if (need_to_encode == 0) {
1708 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1709 Encoded = (char*) malloc (EncodedMaxLen);
1711 for (i = 0; i < nColons; i++)
1712 source[AddrPtr[i]++] = '\0';
1713 /* TODO: if libidn, this might get larger*/
1714 user = malloc(SourceLen + 1);
1715 node = malloc(SourceLen + 1);
1716 name = malloc(SourceLen + 1);
1720 for (i = 0; i < nColons && nPtr != NULL; i++) {
1721 nmax = EncodedMaxLen - (nPtr - Encoded);
1723 process_rfc822_addr(&source[AddrPtr[i]],
1727 /* TODO: libIDN here ! */
1728 if (IsEmptyStr(name)) {
1729 n = snprintf(nPtr, nmax,
1730 (i==0)?"%s@%s" : ",%s@%s",
1734 EncodedName = rfc2047encode(name, strlen(name));
1735 n = snprintf(nPtr, nmax,
1736 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1737 EncodedName, user, node);
1742 n = snprintf(nPtr, nmax,
1743 (i==0)?"%s" : ",%s",
1744 &source[AddrPtr[i]]);
1750 ptr = (char*) malloc(EncodedMaxLen * 2);
1751 memcpy(ptr, Encoded, EncodedMaxLen);
1752 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1753 free(Encoded), Encoded = ptr;
1755 i--; /* do it once more with properly lengthened buffer */
1758 for (i = 0; i < nColons; i++)
1759 source[--AddrPtr[i]] = ',';
1770 /* If the last item in a list of recipients was truncated to a partial address,
1771 * remove it completely in order to avoid choking libSieve
1773 void sanitize_truncated_recipient(char *str)
1776 if (num_tokens(str, ',') < 2) return;
1778 int len = strlen(str);
1779 if (len < 900) return;
1780 if (len > 998) str[998] = 0;
1782 char *cptr = strrchr(str, ',');
1785 char *lptr = strchr(cptr, '<');
1786 char *rptr = strchr(cptr, '>');
1788 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1794 void OutputCtdlMsgHeaders(
1795 struct CtdlMessage *TheMessage,
1796 int do_proto) /* do Citadel protocol responses? */
1802 char display_name[256];
1804 /* begin header processing loop for Citadel message format */
1805 safestrncpy(display_name, "<unknown>", sizeof display_name);
1806 if (TheMessage->cm_fields['A']) {
1807 strcpy(buf, TheMessage->cm_fields['A']);
1808 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1809 safestrncpy(display_name, "****", sizeof display_name);
1811 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1812 safestrncpy(display_name, "anonymous", sizeof display_name);
1815 safestrncpy(display_name, buf, sizeof display_name);
1817 if ((is_room_aide())
1818 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1819 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1820 size_t tmp = strlen(display_name);
1821 snprintf(&display_name[tmp],
1822 sizeof display_name - tmp,
1827 /* Don't show Internet address for users on the
1828 * local Citadel network.
1831 if (TheMessage->cm_fields['N'] != NULL)
1832 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1833 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1837 /* Now spew the header fields in the order we like them. */
1838 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1839 for (i=0; i<n; ++i) {
1840 k = (int) allkeys[i];
1842 if ( (TheMessage->cm_fields[k] != NULL)
1843 && (msgkeys[k] != NULL) ) {
1844 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1845 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1848 if (do_proto) cprintf("%s=%s\n",
1852 else if ((k == 'F') && (suppress_f)) {
1855 /* Masquerade display name if needed */
1857 if (do_proto) cprintf("%s=%s\n",
1859 TheMessage->cm_fields[k]
1868 void OutputRFC822MsgHeaders(
1869 struct CtdlMessage *TheMessage,
1870 int flags, /* should the bessage be exported clean */
1872 char *mid, long sizeof_mid,
1873 char *suser, long sizeof_suser,
1874 char *luser, long sizeof_luser,
1875 char *fuser, long sizeof_fuser,
1876 char *snode, long sizeof_snode)
1878 char datestamp[100];
1879 int subject_found = 0;
1885 for (i = 0; i < 256; ++i) {
1886 if (TheMessage->cm_fields[i]) {
1887 mptr = mpptr = TheMessage->cm_fields[i];
1890 safestrncpy(luser, mptr, sizeof_luser);
1891 safestrncpy(suser, mptr, sizeof_suser);
1893 else if (i == 'Y') {
1894 if ((flags & QP_EADDR) != 0) {
1895 mptr = qp_encode_email_addrs(mptr);
1897 sanitize_truncated_recipient(mptr);
1898 cprintf("CC: %s%s", mptr, nl);
1900 else if (i == 'P') {
1901 cprintf("Return-Path: %s%s", mptr, nl);
1903 else if (i == 'L') {
1904 cprintf("List-ID: %s%s", mptr, nl);
1906 else if (i == 'V') {
1907 if ((flags & QP_EADDR) != 0)
1908 mptr = qp_encode_email_addrs(mptr);
1909 cprintf("Envelope-To: %s%s", mptr, nl);
1911 else if (i == 'U') {
1912 cprintf("Subject: %s%s", mptr, nl);
1916 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1918 safestrncpy(fuser, mptr, sizeof_fuser);
1919 /* else if (i == 'O')
1920 cprintf("X-Citadel-Room: %s%s",
1923 safestrncpy(snode, mptr, sizeof_snode);
1926 if (haschar(mptr, '@') == 0)
1928 sanitize_truncated_recipient(mptr);
1929 cprintf("To: %s@%s", mptr, config.c_fqdn);
1934 if ((flags & QP_EADDR) != 0) {
1935 mptr = qp_encode_email_addrs(mptr);
1937 sanitize_truncated_recipient(mptr);
1938 cprintf("To: %s", mptr);
1942 else if (i == 'T') {
1943 datestring(datestamp, sizeof datestamp,
1944 atol(mptr), DATESTRING_RFC822);
1945 cprintf("Date: %s%s", datestamp, nl);
1947 else if (i == 'W') {
1948 cprintf("References: ");
1949 k = num_tokens(mptr, '|');
1950 for (j=0; j<k; ++j) {
1951 extract_token(buf, mptr, j, '|', sizeof buf);
1952 cprintf("<%s>", buf);
1961 else if (i == 'K') {
1962 cprintf("Reply-To: <%s>%s", mptr, nl);
1968 if (subject_found == 0) {
1969 cprintf("Subject: (no subject)%s", nl);
1974 void Dump_RFC822HeadersBody(
1975 struct CtdlMessage *TheMessage,
1976 int headers_only, /* eschew the message body? */
1977 int flags, /* should the bessage be exported clean? */
1981 cit_uint8_t prev_ch;
1983 const char *StartOfText = StrBufNOTNULL;
1986 int nllen = strlen(nl);
1990 mptr = TheMessage->cm_fields['M'];
1994 while (*mptr != '\0') {
1995 if (*mptr == '\r') {
2002 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2004 eoh = *(mptr+1) == '\n';
2008 StartOfText = strchr(StartOfText, '\n');
2009 StartOfText = strchr(StartOfText, '\n');
2012 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2013 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2014 ((headers_only != HEADERS_NONE) &&
2015 (headers_only != HEADERS_ONLY))
2017 if (*mptr == '\n') {
2018 memcpy(&outbuf[outlen], nl, nllen);
2020 outbuf[outlen] = '\0';
2023 outbuf[outlen++] = *mptr;
2027 if (flags & ESC_DOT)
2029 if ((prev_ch == '\n') &&
2031 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2033 outbuf[outlen++] = '.';
2038 if (outlen > 1000) {
2039 if (client_write(outbuf, outlen) == -1)
2041 syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2048 rc = client_write(outbuf, outlen);
2055 /* If the format type on disk is 1 (fixed-format), then we want
2056 * everything to be output completely literally ... regardless of
2057 * what message transfer format is in use.
2059 void DumpFormatFixed(
2060 struct CtdlMessage *TheMessage,
2061 int mode, /* how would you like that message? */
2068 int nllen = strlen (nl);
2071 mptr = TheMessage->cm_fields['M'];
2073 if (mode == MT_MIME) {
2074 cprintf("Content-type: text/plain\n\n");
2078 while (ch = *mptr++, ch > 0) {
2082 if ((buflen > 250) && (!xlline)){
2086 while ((buflen > 0) &&
2087 (!isspace(buf[buflen])))
2093 mptr -= tbuflen - buflen;
2098 /* if we reach the outer bounds of our buffer,
2099 abort without respect what whe purge. */
2102 (buflen > SIZ - nllen - 2)))
2106 memcpy (&buf[buflen], nl, nllen);
2110 if (client_write(buf, buflen) == -1)
2112 syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2124 if (!IsEmptyStr(buf))
2125 cprintf("%s%s", buf, nl);
2129 * Get a message off disk. (returns om_* values found in msgbase.h)
2131 int CtdlOutputPreLoadedMsg(
2132 struct CtdlMessage *TheMessage,
2133 int mode, /* how would you like that message? */
2134 int headers_only, /* eschew the message body? */
2135 int do_proto, /* do Citadel protocol responses? */
2136 int crlf, /* Use CRLF newlines instead of LF? */
2137 int flags /* should the bessage be exported clean? */
2141 const char *nl; /* newline string */
2144 /* Buffers needed for RFC822 translation. These are all filled
2145 * using functions that are bounds-checked, and therefore we can
2146 * make them substantially smaller than SIZ.
2154 syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2155 ((TheMessage == NULL) ? "NULL" : "not null"),
2156 mode, headers_only, do_proto, crlf);
2158 strcpy(mid, "unknown");
2159 nl = (crlf ? "\r\n" : "\n");
2161 if (!is_valid_message(TheMessage)) {
2163 "ERROR: invalid preloaded message for output\n");
2165 return(om_no_such_msg);
2168 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2169 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2171 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2172 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2175 /* Are we downloading a MIME component? */
2176 if (mode == MT_DOWNLOAD) {
2177 if (TheMessage->cm_format_type != FMT_RFC822) {
2179 cprintf("%d This is not a MIME message.\n",
2180 ERROR + ILLEGAL_VALUE);
2181 } else if (CC->download_fp != NULL) {
2182 if (do_proto) cprintf(
2183 "%d You already have a download open.\n",
2184 ERROR + RESOURCE_BUSY);
2186 /* Parse the message text component */
2187 mptr = TheMessage->cm_fields['M'];
2188 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2189 /* If there's no file open by this time, the requested
2190 * section wasn't found, so print an error
2192 if (CC->download_fp == NULL) {
2193 if (do_proto) cprintf(
2194 "%d Section %s not found.\n",
2195 ERROR + FILE_NOT_FOUND,
2196 CC->download_desired_section);
2199 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2202 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2203 * in a single server operation instead of opening a download file.
2205 if (mode == MT_SPEW_SECTION) {
2206 if (TheMessage->cm_format_type != FMT_RFC822) {
2208 cprintf("%d This is not a MIME message.\n",
2209 ERROR + ILLEGAL_VALUE);
2211 /* Parse the message text component */
2214 mptr = TheMessage->cm_fields['M'];
2215 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2216 /* If section wasn't found, print an error
2219 if (do_proto) cprintf(
2220 "%d Section %s not found.\n",
2221 ERROR + FILE_NOT_FOUND,
2222 CC->download_desired_section);
2225 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2228 /* now for the user-mode message reading loops */
2229 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2231 /* Does the caller want to skip the headers? */
2232 if (headers_only == HEADERS_NONE) goto START_TEXT;
2234 /* Tell the client which format type we're using. */
2235 if ( (mode == MT_CITADEL) && (do_proto) ) {
2236 cprintf("type=%d\n", TheMessage->cm_format_type);
2239 /* nhdr=yes means that we're only displaying headers, no body */
2240 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2241 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2244 cprintf("nhdr=yes\n");
2247 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2248 OutputCtdlMsgHeaders(TheMessage, do_proto);
2251 /* begin header processing loop for RFC822 transfer format */
2255 strcpy(snode, NODENAME);
2256 if (mode == MT_RFC822)
2257 OutputRFC822MsgHeaders(
2262 suser, sizeof(suser),
2263 luser, sizeof(luser),
2264 fuser, sizeof(fuser),
2265 snode, sizeof(snode)
2269 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2270 suser[i] = tolower(suser[i]);
2271 if (!isalnum(suser[i])) suser[i]='_';
2274 if (mode == MT_RFC822) {
2275 if (!strcasecmp(snode, NODENAME)) {
2276 safestrncpy(snode, FQDN, sizeof snode);
2279 /* Construct a fun message id */
2280 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2281 if (strchr(mid, '@')==NULL) {
2282 cprintf("@%s", snode);
2286 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2287 cprintf("From: \"----\" <x@x.org>%s", nl);
2289 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2290 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2292 else if (!IsEmptyStr(fuser)) {
2293 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2296 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2299 /* Blank line signifying RFC822 end-of-headers */
2300 if (TheMessage->cm_format_type != FMT_RFC822) {
2305 /* end header processing loop ... at this point, we're in the text */
2307 if (headers_only == HEADERS_FAST) goto DONE;
2309 /* Tell the client about the MIME parts in this message */
2310 if (TheMessage->cm_format_type == FMT_RFC822) {
2311 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2312 mptr = TheMessage->cm_fields['M'];
2313 memset(&ma, 0, sizeof(struct ma_info));
2314 mime_parser(mptr, NULL,
2315 (do_proto ? *list_this_part : NULL),
2316 (do_proto ? *list_this_pref : NULL),
2317 (do_proto ? *list_this_suff : NULL),
2320 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2321 Dump_RFC822HeadersBody(
2330 if (headers_only == HEADERS_ONLY) {
2334 /* signify start of msg text */
2335 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2336 if (do_proto) cprintf("text\n");
2339 if (TheMessage->cm_format_type == FMT_FIXED)
2342 mode, /* how would you like that message? */
2345 /* If the message on disk is format 0 (Citadel vari-format), we
2346 * output using the formatter at 80 columns. This is the final output
2347 * form if the transfer format is RFC822, but if the transfer format
2348 * is Citadel proprietary, it'll still work, because the indentation
2349 * for new paragraphs is correct and the client will reformat the
2350 * message to the reader's screen width.
2352 if (TheMessage->cm_format_type == FMT_CITADEL) {
2353 mptr = TheMessage->cm_fields['M'];
2355 if (mode == MT_MIME) {
2356 cprintf("Content-type: text/x-citadel-variformat\n\n");
2361 /* If the message on disk is format 4 (MIME), we've gotta hand it
2362 * off to the MIME parser. The client has already been told that
2363 * this message is format 1 (fixed format), so the callback function
2364 * we use will display those parts as-is.
2366 if (TheMessage->cm_format_type == FMT_RFC822) {
2367 memset(&ma, 0, sizeof(struct ma_info));
2369 if (mode == MT_MIME) {
2370 ma.use_fo_hooks = 0;
2371 strcpy(ma.chosen_part, "1");
2372 ma.chosen_pref = 9999;
2373 ma.dont_decode = CC->msg4_dont_decode;
2374 mime_parser(mptr, NULL,
2375 *choose_preferred, *fixed_output_pre,
2376 *fixed_output_post, (void *)&ma, 1);
2377 mime_parser(mptr, NULL,
2378 *output_preferred, NULL, NULL, (void *)&ma, 1);
2381 ma.use_fo_hooks = 1;
2382 mime_parser(mptr, NULL,
2383 *fixed_output, *fixed_output_pre,
2384 *fixed_output_post, (void *)&ma, 0);
2389 DONE: /* now we're done */
2390 if (do_proto) cprintf("000\n");
2396 * display a message (mode 0 - Citadel proprietary)
2398 void cmd_msg0(char *cmdbuf)
2401 int headers_only = HEADERS_ALL;
2403 msgid = extract_long(cmdbuf, 0);
2404 headers_only = extract_int(cmdbuf, 1);
2406 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2412 * display a message (mode 2 - RFC822)
2414 void cmd_msg2(char *cmdbuf)
2417 int headers_only = HEADERS_ALL;
2419 msgid = extract_long(cmdbuf, 0);
2420 headers_only = extract_int(cmdbuf, 1);
2422 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2428 * display a message (mode 3 - IGnet raw format - internal programs only)
2430 void cmd_msg3(char *cmdbuf)
2433 struct CtdlMessage *msg = NULL;
2436 if (CC->internal_pgm == 0) {
2437 cprintf("%d This command is for internal programs only.\n",
2438 ERROR + HIGHER_ACCESS_REQUIRED);
2442 msgnum = extract_long(cmdbuf, 0);
2443 msg = CtdlFetchMessage(msgnum, 1);
2445 cprintf("%d Message %ld not found.\n",
2446 ERROR + MESSAGE_NOT_FOUND, msgnum);
2450 serialize_message(&smr, msg);
2451 CtdlFreeMessage(msg);
2454 cprintf("%d Unable to serialize message\n",
2455 ERROR + INTERNAL_ERROR);
2459 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2460 client_write((char *)smr.ser, (int)smr.len);
2467 * Display a message using MIME content types
2469 void cmd_msg4(char *cmdbuf)
2474 msgid = extract_long(cmdbuf, 0);
2475 extract_token(section, cmdbuf, 1, '|', sizeof section);
2476 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2482 * Client tells us its preferred message format(s)
2484 void cmd_msgp(char *cmdbuf)
2486 if (!strcasecmp(cmdbuf, "dont_decode")) {
2487 CC->msg4_dont_decode = 1;
2488 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2491 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2492 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2498 * Open a component of a MIME message as a download file
2500 void cmd_opna(char *cmdbuf)
2503 char desired_section[128];
2505 msgid = extract_long(cmdbuf, 0);
2506 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2507 safestrncpy(CC->download_desired_section, desired_section,
2508 sizeof CC->download_desired_section);
2509 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2514 * Open a component of a MIME message and transmit it all at once
2516 void cmd_dlat(char *cmdbuf)
2519 char desired_section[128];
2521 msgid = extract_long(cmdbuf, 0);
2522 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2523 safestrncpy(CC->download_desired_section, desired_section,
2524 sizeof CC->download_desired_section);
2525 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2530 * Save one or more message pointers into a specified room
2531 * (Returns 0 for success, nonzero for failure)
2532 * roomname may be NULL to use the current room
2534 * Note that the 'supplied_msg' field may be set to NULL, in which case
2535 * the message will be fetched from disk, by number, if we need to perform
2536 * replication checks. This adds an additional database read, so if the
2537 * caller already has the message in memory then it should be supplied. (Obviously
2538 * this mode of operation only works if we're saving a single message.)
2540 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2541 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2544 char hold_rm[ROOMNAMELEN];
2545 struct cdbdata *cdbfr;
2548 long highest_msg = 0L;
2551 struct CtdlMessage *msg = NULL;
2553 long *msgs_to_be_merged = NULL;
2554 int num_msgs_to_be_merged = 0;
2557 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2558 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2561 strcpy(hold_rm, CC->room.QRname);
2564 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2565 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2566 if (num_newmsgs > 1) supplied_msg = NULL;
2568 /* Now the regular stuff */
2569 if (CtdlGetRoomLock(&CC->room,
2570 ((roomname != NULL) ? roomname : CC->room.QRname) )
2572 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2573 return(ERROR + ROOM_NOT_FOUND);
2577 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2578 num_msgs_to_be_merged = 0;
2581 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2582 if (cdbfr == NULL) {
2586 msglist = (long *) cdbfr->ptr;
2587 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2588 num_msgs = cdbfr->len / sizeof(long);
2593 /* Create a list of msgid's which were supplied by the caller, but do
2594 * not already exist in the target room. It is absolutely taboo to
2595 * have more than one reference to the same message in a room.
2597 for (i=0; i<num_newmsgs; ++i) {
2599 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2600 if (msglist[j] == newmsgidlist[i]) {
2605 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2609 syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2612 * Now merge the new messages
2614 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2615 if (msglist == NULL) {
2616 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2618 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2619 num_msgs += num_msgs_to_be_merged;
2621 /* Sort the message list, so all the msgid's are in order */
2622 num_msgs = sort_msglist(msglist, num_msgs);
2624 /* Determine the highest message number */
2625 highest_msg = msglist[num_msgs - 1];
2627 /* Write it back to disk. */
2628 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2629 msglist, (int)(num_msgs * sizeof(long)));
2631 /* Free up the memory we used. */
2634 /* Update the highest-message pointer and unlock the room. */
2635 CC->room.QRhighest = highest_msg;
2636 CtdlPutRoomLock(&CC->room);
2638 /* Perform replication checks if necessary */
2639 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2640 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2642 for (i=0; i<num_msgs_to_be_merged; ++i) {
2643 msgid = msgs_to_be_merged[i];
2645 if (supplied_msg != NULL) {
2649 msg = CtdlFetchMessage(msgid, 0);
2653 ReplicationChecks(msg);
2655 /* If the message has an Exclusive ID, index that... */
2656 if (msg->cm_fields['E'] != NULL) {
2657 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2660 /* Free up the memory we may have allocated */
2661 if (msg != supplied_msg) {
2662 CtdlFreeMessage(msg);
2670 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2673 /* Submit this room for processing by hooks */
2674 PerformRoomHooks(&CC->room);
2676 /* Go back to the room we were in before we wandered here... */
2677 CtdlGetRoom(&CC->room, hold_rm);
2679 /* Bump the reference count for all messages which were merged */
2680 if (!suppress_refcount_adj) {
2681 for (i=0; i<num_msgs_to_be_merged; ++i) {
2682 AdjRefCount(msgs_to_be_merged[i], +1);
2686 /* Free up memory... */
2687 if (msgs_to_be_merged != NULL) {
2688 free(msgs_to_be_merged);
2691 /* Return success. */
2697 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2700 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2701 int do_repl_check, struct CtdlMessage *supplied_msg)
2703 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2710 * Message base operation to save a new message to the message store
2711 * (returns new message number)
2713 * This is the back end for CtdlSubmitMsg() and should not be directly
2714 * called by server-side modules.
2717 long send_message(struct CtdlMessage *msg) {
2725 /* Get a new message number */
2726 newmsgid = get_new_message_number();
2727 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2729 /* Generate an ID if we don't have one already */
2730 if (msg->cm_fields['I']==NULL) {
2731 msg->cm_fields['I'] = strdup(msgidbuf);
2734 /* If the message is big, set its body aside for storage elsewhere */
2735 if (msg->cm_fields['M'] != NULL) {
2736 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2738 holdM = msg->cm_fields['M'];
2739 msg->cm_fields['M'] = NULL;
2743 /* Serialize our data structure for storage in the database */
2744 serialize_message(&smr, msg);
2747 msg->cm_fields['M'] = holdM;
2751 cprintf("%d Unable to serialize message\n",
2752 ERROR + INTERNAL_ERROR);
2756 /* Write our little bundle of joy into the message base */
2757 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2758 smr.ser, smr.len) < 0) {
2759 syslog(LOG_ERR, "Can't store message\n");
2763 cdb_store(CDB_BIGMSGS,
2773 /* Free the memory we used for the serialized message */
2776 /* Return the *local* message ID to the caller
2777 * (even if we're storing an incoming network message)
2785 * Serialize a struct CtdlMessage into the format used on disk and network.
2787 * This function loads up a "struct ser_ret" (defined in server.h) which
2788 * contains the length of the serialized message and a pointer to the
2789 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2791 void serialize_message(struct ser_ret *ret, /* return values */
2792 struct CtdlMessage *msg) /* unserialized msg */
2794 size_t wlen, fieldlen;
2796 static char *forder = FORDER;
2799 * Check for valid message format
2801 if (is_valid_message(msg) == 0) {
2802 syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2809 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2810 ret->len = ret->len +
2811 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2813 ret->ser = malloc(ret->len);
2814 if (ret->ser == NULL) {
2815 syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2816 (long)ret->len, strerror(errno));
2823 ret->ser[1] = msg->cm_anon_type;
2824 ret->ser[2] = msg->cm_format_type;
2827 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2828 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2829 ret->ser[wlen++] = (char)forder[i];
2830 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2831 wlen = wlen + fieldlen + 1;
2833 if (ret->len != wlen) syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2834 (long)ret->len, (long)wlen);
2841 * Serialize a struct CtdlMessage into the format used on disk and network.
2843 * This function loads up a "struct ser_ret" (defined in server.h) which
2844 * contains the length of the serialized message and a pointer to the
2845 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2847 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2848 long Siz) /* how many chars ? */
2852 static char *forder = FORDER;
2856 * Check for valid message format
2858 if (is_valid_message(msg) == 0) {
2859 syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
2863 buf = (char*) malloc (Siz + 1);
2867 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2868 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2869 msg->cm_fields[(int)forder[i]]);
2870 if (client_write (buf, strlen(buf)) == -1)
2872 syslog(LOG_ERR, "dump_message(): aborting due to write failure.\n");
2883 * Check to see if any messages already exist in the current room which
2884 * carry the same Exclusive ID as this one. If any are found, delete them.
2886 void ReplicationChecks(struct CtdlMessage *msg) {
2887 long old_msgnum = (-1L);
2889 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2891 syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2894 /* No exclusive id? Don't do anything. */
2895 if (msg == NULL) return;
2896 if (msg->cm_fields['E'] == NULL) return;
2897 if (IsEmptyStr(msg->cm_fields['E'])) return;
2898 /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2899 msg->cm_fields['E'], CC->room.QRname);*/
2901 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2902 if (old_msgnum > 0L) {
2903 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2904 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2911 * Save a message to disk and submit it into the delivery system.
2913 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2914 struct recptypes *recps, /* recipients (if mail) */
2915 const char *force, /* force a particular room? */
2916 int flags /* should the message be exported clean? */
2918 char submit_filename[128];
2919 char generated_timestamp[32];
2920 char hold_rm[ROOMNAMELEN];
2921 char actual_rm[ROOMNAMELEN];
2922 char force_room[ROOMNAMELEN];
2923 char content_type[SIZ]; /* We have to learn this */
2924 char recipient[SIZ];
2926 const char *mptr = NULL;
2927 struct ctdluser userbuf;
2929 struct MetaData smi;
2930 FILE *network_fp = NULL;
2931 static int seqnum = 1;
2932 struct CtdlMessage *imsg = NULL;
2934 size_t instr_alloc = 0;
2936 char *hold_R, *hold_D;
2937 char *collected_addresses = NULL;
2938 struct addresses_to_be_filed *aptr = NULL;
2939 StrBuf *saved_rfc822_version = NULL;
2940 int qualified_for_journaling = 0;
2941 CitContext *CCC = MyContext();
2942 char bounce_to[1024] = "";
2946 syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
2947 if (is_valid_message(msg) == 0) return(-1); /* self check */
2949 /* If this message has no timestamp, we take the liberty of
2950 * giving it one, right now.
2952 if (msg->cm_fields['T'] == NULL) {
2953 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2954 msg->cm_fields['T'] = strdup(generated_timestamp);
2957 /* If this message has no path, we generate one.
2959 if (msg->cm_fields['P'] == NULL) {
2960 if (msg->cm_fields['A'] != NULL) {
2961 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2962 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2963 if (isspace(msg->cm_fields['P'][a])) {
2964 msg->cm_fields['P'][a] = ' ';
2969 msg->cm_fields['P'] = strdup("unknown");
2973 if (force == NULL) {
2974 strcpy(force_room, "");
2977 strcpy(force_room, force);
2980 /* Learn about what's inside, because it's what's inside that counts */
2981 if (msg->cm_fields['M'] == NULL) {
2982 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
2986 switch (msg->cm_format_type) {
2988 strcpy(content_type, "text/x-citadel-variformat");
2991 strcpy(content_type, "text/plain");
2994 strcpy(content_type, "text/plain");
2995 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2998 safestrncpy(content_type, &mptr[13], sizeof content_type);
2999 striplt(content_type);
3000 aptr = content_type;
3001 while (!IsEmptyStr(aptr)) {
3013 /* Goto the correct room */
3014 syslog(LOG_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3015 strcpy(hold_rm, CCC->room.QRname);
3016 strcpy(actual_rm, CCC->room.QRname);
3017 if (recps != NULL) {
3018 strcpy(actual_rm, SENTITEMS);
3021 /* If the user is a twit, move to the twit room for posting */
3023 if (CCC->user.axlevel == AxProbU) {
3024 strcpy(hold_rm, actual_rm);
3025 strcpy(actual_rm, config.c_twitroom);
3026 syslog(LOG_DEBUG, "Diverting to twit room\n");
3030 /* ...or if this message is destined for Aide> then go there. */
3031 if (!IsEmptyStr(force_room)) {
3032 strcpy(actual_rm, force_room);
3035 syslog(LOG_DEBUG, "Final selection: %s\n", actual_rm);
3036 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3037 /* CtdlGetRoom(&CCC->room, actual_rm); */
3038 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3042 * If this message has no O (room) field, generate one.
3044 if (msg->cm_fields['O'] == NULL) {
3045 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3048 /* Perform "before save" hooks (aborting if any return nonzero) */
3049 syslog(LOG_DEBUG, "Performing before-save hooks\n");
3050 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3053 * If this message has an Exclusive ID, and the room is replication
3054 * checking enabled, then do replication checks.
3056 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3057 ReplicationChecks(msg);
3060 /* Save it to disk */
3061 syslog(LOG_DEBUG, "Saving to disk\n");
3062 newmsgid = send_message(msg);
3063 if (newmsgid <= 0L) return(-5);
3065 /* Write a supplemental message info record. This doesn't have to
3066 * be a critical section because nobody else knows about this message
3069 syslog(LOG_DEBUG, "Creating MetaData record\n");
3070 memset(&smi, 0, sizeof(struct MetaData));
3071 smi.meta_msgnum = newmsgid;
3072 smi.meta_refcount = 0;
3073 safestrncpy(smi.meta_content_type, content_type,
3074 sizeof smi.meta_content_type);
3077 * Measure how big this message will be when rendered as RFC822.
3078 * We do this for two reasons:
3079 * 1. We need the RFC822 length for the new metadata record, so the
3080 * POP and IMAP services don't have to calculate message lengths
3081 * while the user is waiting (multiplied by potentially hundreds
3082 * or thousands of messages).
3083 * 2. If journaling is enabled, we will need an RFC822 version of the
3084 * message to attach to the journalized copy.
3086 if (CCC->redirect_buffer != NULL) {
3087 syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3090 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3091 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3092 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3093 saved_rfc822_version = CCC->redirect_buffer;
3094 CCC->redirect_buffer = NULL;
3098 /* Now figure out where to store the pointers */
3099 syslog(LOG_DEBUG, "Storing pointers\n");
3101 /* If this is being done by the networker delivering a private
3102 * message, we want to BYPASS saving the sender's copy (because there
3103 * is no local sender; it would otherwise go to the Trashcan).
3105 if ((!CCC->internal_pgm) || (recps == NULL)) {
3106 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3107 syslog(LOG_ERR, "ERROR saving message pointer!\n");
3108 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3112 /* For internet mail, drop a copy in the outbound queue room */
3113 if ((recps != NULL) && (recps->num_internet > 0)) {
3114 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3117 /* If other rooms are specified, drop them there too. */
3118 if ((recps != NULL) && (recps->num_room > 0))
3119 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3120 extract_token(recipient, recps->recp_room, i,
3121 '|', sizeof recipient);
3122 syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
3123 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3126 /* Bump this user's messages posted counter. */
3127 syslog(LOG_DEBUG, "Updating user\n");
3128 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3129 CCC->user.posted = CCC->user.posted + 1;
3130 CtdlPutUserLock(&CCC->user);
3132 /* Decide where bounces need to be delivered */
3133 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3134 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3136 else if (CCC->logged_in) {
3137 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3140 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3143 /* If this is private, local mail, make a copy in the
3144 * recipient's mailbox and bump the reference count.
3146 if ((recps != NULL) && (recps->num_local > 0))
3147 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3148 extract_token(recipient, recps->recp_local, i,
3149 '|', sizeof recipient);
3150 syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3152 if (CtdlGetUser(&userbuf, recipient) == 0) {
3153 // Add a flag so the Funambol module knows its mail
3154 msg->cm_fields['W'] = strdup(recipient);
3155 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3156 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3157 CtdlBumpNewMailCounter(userbuf.usernum);
3158 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3159 /* Generate a instruction message for the Funambol notification
3160 * server, in the same style as the SMTP queue
3163 instr = malloc(instr_alloc);
3164 snprintf(instr, instr_alloc,
3165 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3167 SPOOLMIME, newmsgid, (long)time(NULL),
3171 imsg = malloc(sizeof(struct CtdlMessage));
3172 memset(imsg, 0, sizeof(struct CtdlMessage));
3173 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3174 imsg->cm_anon_type = MES_NORMAL;
3175 imsg->cm_format_type = FMT_RFC822;
3176 imsg->cm_fields['A'] = strdup("Citadel");
3177 imsg->cm_fields['J'] = strdup("do not journal");
3178 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3179 imsg->cm_fields['W'] = strdup(recipient);
3180 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3181 CtdlFreeMessage(imsg);
3185 syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3186 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3191 /* Perform "after save" hooks */
3192 syslog(LOG_DEBUG, "Performing after-save hooks\n");
3193 PerformMessageHooks(msg, EVT_AFTERSAVE);
3195 /* For IGnet mail, we have to save a new copy into the spooler for
3196 * each recipient, with the R and D fields set to the recipient and
3197 * destination-node. This has two ugly side effects: all other
3198 * recipients end up being unlisted in this recipient's copy of the
3199 * message, and it has to deliver multiple messages to the same
3200 * node. We'll revisit this again in a year or so when everyone has
3201 * a network spool receiver that can handle the new style messages.
3203 if ((recps != NULL) && (recps->num_ignet > 0))
3204 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3205 extract_token(recipient, recps->recp_ignet, i,
3206 '|', sizeof recipient);
3208 hold_R = msg->cm_fields['R'];
3209 hold_D = msg->cm_fields['D'];
3210 msg->cm_fields['R'] = malloc(SIZ);
3211 msg->cm_fields['D'] = malloc(128);
3212 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3213 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3215 serialize_message(&smr, msg);
3217 snprintf(submit_filename, sizeof submit_filename,
3218 "%s/netmail.%04lx.%04x.%04x",
3220 (long) getpid(), CCC->cs_pid, ++seqnum);
3221 network_fp = fopen(submit_filename, "wb+");
3222 if (network_fp != NULL) {
3223 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3229 free(msg->cm_fields['R']);
3230 free(msg->cm_fields['D']);
3231 msg->cm_fields['R'] = hold_R;
3232 msg->cm_fields['D'] = hold_D;
3235 /* Go back to the room we started from */
3236 syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3237 if (strcasecmp(hold_rm, CCC->room.QRname))
3238 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3240 /* For internet mail, generate delivery instructions.
3241 * Yes, this is recursive. Deal with it. Infinite recursion does
3242 * not happen because the delivery instructions message does not
3243 * contain a recipient.
3245 if ((recps != NULL) && (recps->num_internet > 0)) {
3246 syslog(LOG_DEBUG, "Generating delivery instructions\n");
3248 instr = malloc(instr_alloc);
3249 snprintf(instr, instr_alloc,
3250 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3252 SPOOLMIME, newmsgid, (long)time(NULL),
3256 if (recps->envelope_from != NULL) {
3257 tmp = strlen(instr);
3258 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3261 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3262 tmp = strlen(instr);
3263 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3264 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3265 instr_alloc = instr_alloc * 2;
3266 instr = realloc(instr, instr_alloc);
3268 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3271 imsg = malloc(sizeof(struct CtdlMessage));
3272 memset(imsg, 0, sizeof(struct CtdlMessage));
3273 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3274 imsg->cm_anon_type = MES_NORMAL;
3275 imsg->cm_format_type = FMT_RFC822;
3276 imsg->cm_fields['A'] = strdup("Citadel");
3277 imsg->cm_fields['J'] = strdup("do not journal");
3278 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3279 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3280 CtdlFreeMessage(imsg);
3284 * Any addresses to harvest for someone's address book?
3286 if ( (CCC->logged_in) && (recps != NULL) ) {
3287 collected_addresses = harvest_collected_addresses(msg);
3290 if (collected_addresses != NULL) {
3291 aptr = (struct addresses_to_be_filed *)
3292 malloc(sizeof(struct addresses_to_be_filed));
3293 CtdlMailboxName(actual_rm, sizeof actual_rm,
3294 &CCC->user, USERCONTACTSROOM);
3295 aptr->roomname = strdup(actual_rm);
3296 aptr->collected_addresses = collected_addresses;
3297 begin_critical_section(S_ATBF);
3300 end_critical_section(S_ATBF);
3304 * Determine whether this message qualifies for journaling.
3306 if (msg->cm_fields['J'] != NULL) {
3307 qualified_for_journaling = 0;
3310 if (recps == NULL) {
3311 qualified_for_journaling = config.c_journal_pubmsgs;
3313 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3314 qualified_for_journaling = config.c_journal_email;
3317 qualified_for_journaling = config.c_journal_pubmsgs;
3322 * Do we have to perform journaling? If so, hand off the saved
3323 * RFC822 version will be handed off to the journaler for background
3324 * submit. Otherwise, we have to free the memory ourselves.
3326 if (saved_rfc822_version != NULL) {
3327 if (qualified_for_journaling) {
3328 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3331 FreeStrBuf(&saved_rfc822_version);
3341 void aide_message (char *text, char *subject)
3343 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3348 * Convenience function for generating small administrative messages.
3350 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3351 int format_type, const char *subject)
3353 struct CtdlMessage *msg;
3354 struct recptypes *recp = NULL;
3356 msg = malloc(sizeof(struct CtdlMessage));
3357 memset(msg, 0, sizeof(struct CtdlMessage));
3358 msg->cm_magic = CTDLMESSAGE_MAGIC;
3359 msg->cm_anon_type = MES_NORMAL;
3360 msg->cm_format_type = format_type;
3363 msg->cm_fields['A'] = strdup(from);
3365 else if (fromaddr != NULL) {
3366 msg->cm_fields['A'] = strdup(fromaddr);
3367 if (strchr(msg->cm_fields['A'], '@')) {
3368 *strchr(msg->cm_fields['A'], '@') = 0;
3372 msg->cm_fields['A'] = strdup("Citadel");
3375 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3376 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3377 msg->cm_fields['N'] = strdup(NODENAME);
3379 msg->cm_fields['R'] = strdup(to);
3380 recp = validate_recipients(to, NULL, 0);
3382 if (subject != NULL) {
3383 msg->cm_fields['U'] = strdup(subject);
3385 msg->cm_fields['M'] = strdup(text);
3387 CtdlSubmitMsg(msg, recp, room, 0);
3388 CtdlFreeMessage(msg);
3389 if (recp != NULL) free_recipients(recp);
3395 * Back end function used by CtdlMakeMessage() and similar functions
3397 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3399 size_t maxlen, /* maximum message length */
3400 char *exist, /* if non-null, append to it;
3401 exist is ALWAYS freed */
3402 int crlf, /* CRLF newlines instead of LF */
3403 int *sock /* socket handle or 0 for this session's client socket */
3412 LineBuf = NewStrBufPlain(NULL, SIZ);
3413 if (exist == NULL) {
3414 Message = NewStrBufPlain(NULL, 4 * SIZ);
3417 Message = NewStrBufPlain(exist, -1);
3421 /* Do we need to change leading ".." to "." for SMTP escaping? */
3422 if ((tlen == 1) && (*terminator == '.')) {
3426 /* read in the lines of message text one by one */
3429 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3434 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3436 if ((StrLength(LineBuf) == tlen) &&
3437 (!strcmp(ChrPtr(LineBuf), terminator)))
3440 if ( (!flushing) && (!finished) ) {
3442 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3445 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3448 /* Unescape SMTP-style input of two dots at the beginning of the line */
3450 (StrLength(LineBuf) == 2) &&
3451 (!strcmp(ChrPtr(LineBuf), "..")))
3453 StrBufCutLeft(LineBuf, 1);
3456 StrBufAppendBuf(Message, LineBuf, 0);
3459 /* if we've hit the max msg length, flush the rest */
3460 if (StrLength(Message) >= maxlen) flushing = 1;
3462 } while (!finished);
3463 FreeStrBuf(&LineBuf);
3467 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3471 FreeStrBuf(&(*Msg)->MsgBuf);
3477 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3479 size_t maxlen, /* maximum message length */
3480 size_t expectlen, /* if we expect a message, how long should it be? */
3481 char *exist, /* if non-null, append to it;
3482 exist is ALWAYS freed */
3483 long eLen, /* length of exist */
3484 int crlf /* CRLF newlines instead of LF */
3487 ReadAsyncMsg *NewMsg;
3489 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3490 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3492 if (exist == NULL) {
3495 if (expectlen == 0) {
3499 len = expectlen + 10;
3501 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3504 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3507 /* Do we need to change leading ".." to "." for SMTP escaping? */
3508 if ((tlen == 1) && (*terminator == '.')) {
3512 NewMsg->terminator = terminator;
3513 NewMsg->tlen = tlen;
3515 NewMsg->maxlen = maxlen;
3517 NewMsg->crlf = crlf;
3523 * Back end function used by CtdlMakeMessage() and similar functions
3525 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3527 ReadAsyncMsg *ReadMsg;
3528 int MsgFinished = 0;
3529 eReadState Finished = eMustReadMore;
3534 const char *pch = ChrPtr(IO->SendBuf.Buf);
3535 const char *pchh = IO->SendBuf.ReadWritePointer;
3541 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3542 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3543 ((CitContext*)(IO->CitContext))->ServiceName,
3546 fd = fopen(fn, "a+");
3549 ReadMsg = IO->ReadMsg;
3551 /* read in the lines of message text one by one */
3553 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3556 case eMustReadMore: /// read new from socket...
3558 if (IO->RecvBuf.ReadWritePointer != NULL) {
3559 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3560 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3562 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3566 fprintf(fd, "BufferEmpty! \n");
3572 case eBufferNotEmpty: /* shouldn't happen... */
3573 case eReadSuccess: /// done for now...
3575 case eReadFail: /// WHUT?
3581 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3582 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3585 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3588 else if (!ReadMsg->flushing) {
3591 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3594 /* Unescape SMTP-style input of two dots at the beginning of the line */
3595 if ((ReadMsg->dodot) &&
3596 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3597 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3600 fprintf(fd, "UnEscaped!\n");
3602 StrBufCutLeft(IO->IOBuf, 1);
3605 if (ReadMsg->crlf) {
3606 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3609 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3612 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3615 /* if we've hit the max msg length, flush the rest */
3616 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3618 } while (!MsgFinished);
3621 fprintf(fd, "Done with reading; %s.\n, ",
3622 (MsgFinished)?"Message Finished": "FAILED");
3626 return eReadSuccess;
3633 * Back end function used by CtdlMakeMessage() and similar functions
3635 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3637 size_t maxlen, /* maximum message length */
3638 char *exist, /* if non-null, append to it;
3639 exist is ALWAYS freed */
3640 int crlf, /* CRLF newlines instead of LF */
3641 int *sock /* socket handle or 0 for this session's client socket */
3646 Message = CtdlReadMessageBodyBuf(terminator,
3652 if (Message == NULL)
3655 return SmashStrBuf(&Message);
3660 * Build a binary message to be saved on disk.
3661 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3662 * will become part of the message. This means you are no longer
3663 * responsible for managing that memory -- it will be freed along with
3664 * the rest of the fields when CtdlFreeMessage() is called.)
3667 struct CtdlMessage *CtdlMakeMessage(
3668 struct ctdluser *author, /* author's user structure */
3669 char *recipient, /* NULL if it's not mail */
3670 char *recp_cc, /* NULL if it's not mail */
3671 char *room, /* room where it's going */
3672 int type, /* see MES_ types in header file */
3673 int format_type, /* variformat, plain text, MIME... */
3674 char *fake_name, /* who we're masquerading as */
3675 char *my_email, /* which of my email addresses to use (empty is ok) */
3676 char *subject, /* Subject (optional) */
3677 char *supplied_euid, /* ...or NULL if this is irrelevant */
3678 char *preformatted_text, /* ...or NULL to read text from client */
3679 char *references /* Thread references */
3681 char dest_node[256];
3683 struct CtdlMessage *msg;
3685 StrBuf *FakeEncAuthor = NULL;
3687 msg = malloc(sizeof(struct CtdlMessage));
3688 memset(msg, 0, sizeof(struct CtdlMessage));
3689 msg->cm_magic = CTDLMESSAGE_MAGIC;
3690 msg->cm_anon_type = type;
3691 msg->cm_format_type = format_type;
3693 /* Don't confuse the poor folks if it's not routed mail. */
3694 strcpy(dest_node, "");
3696 if (recipient != NULL) striplt(recipient);
3697 if (recp_cc != NULL) striplt(recp_cc);
3699 /* Path or Return-Path */
3700 if (my_email == NULL) my_email = "";
3702 if (!IsEmptyStr(my_email)) {
3703 msg->cm_fields['P'] = strdup(my_email);
3706 snprintf(buf, sizeof buf, "%s", author->fullname);
3707 msg->cm_fields['P'] = strdup(buf);
3709 convert_spaces_to_underscores(msg->cm_fields['P']);
3711 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3712 msg->cm_fields['T'] = strdup(buf);
3714 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3715 FakeAuthor = NewStrBufPlain (fake_name, -1);
3718 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3720 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3721 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3722 FreeStrBuf(&FakeAuthor);
3724 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3725 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3728 msg->cm_fields['O'] = strdup(CC->room.QRname);
3731 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3732 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3734 if ((recipient != NULL) && (recipient[0] != 0)) {
3735 msg->cm_fields['R'] = strdup(recipient);
3737 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3738 msg->cm_fields['Y'] = strdup(recp_cc);
3740 if (dest_node[0] != 0) {
3741 msg->cm_fields['D'] = strdup(dest_node);
3744 if (!IsEmptyStr(my_email)) {
3745 msg->cm_fields['F'] = strdup(my_email);
3747 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3748 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3751 if (subject != NULL) {
3754 length = strlen(subject);
3760 while ((subject[i] != '\0') &&
3761 (IsAscii = isascii(subject[i]) != 0 ))
3764 msg->cm_fields['U'] = strdup(subject);
3765 else /* ok, we've got utf8 in the string. */
3767 msg->cm_fields['U'] = rfc2047encode(subject, length);
3773 if (supplied_euid != NULL) {
3774 msg->cm_fields['E'] = strdup(supplied_euid);
3777 if (references != NULL) {
3778 if (!IsEmptyStr(references)) {
3779 msg->cm_fields['W'] = strdup(references);
3783 if (preformatted_text != NULL) {
3784 msg->cm_fields['M'] = preformatted_text;
3787 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3795 * Check to see whether we have permission to post a message in the current
3796 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3797 * returns 0 on success.
3799 int CtdlDoIHavePermissionToPostInThisRoom(
3802 const char* RemoteIdentifier,
3808 if (!(CC->logged_in) &&
3809 (PostPublic == POST_LOGGED_IN)) {
3810 snprintf(errmsgbuf, n, "Not logged in.");
3811 return (ERROR + NOT_LOGGED_IN);
3813 else if (PostPublic == CHECK_EXISTANCE) {
3814 return (0); // We're Evaling whether a recipient exists
3816 else if (!(CC->logged_in)) {
3818 if ((CC->room.QRflags & QR_READONLY)) {
3819 snprintf(errmsgbuf, n, "Not logged in.");
3820 return (ERROR + NOT_LOGGED_IN);
3822 if (CC->room.QRflags2 & QR2_MODERATED) {
3823 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3824 return (ERROR + NOT_LOGGED_IN);
3826 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3831 if (RemoteIdentifier == NULL)
3833 snprintf(errmsgbuf, n, "Need sender to permit access.");
3834 return (ERROR + USERNAME_REQUIRED);
3837 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3838 begin_critical_section(S_NETCONFIGS);
3839 if (!read_spoolcontrol_file(&sc, filename))
3841 end_critical_section(S_NETCONFIGS);
3842 snprintf(errmsgbuf, n,
3843 "This mailing list only accepts posts from subscribers.");
3844 return (ERROR + NO_SUCH_USER);
3846 end_critical_section(S_NETCONFIGS);
3847 found = is_recipient (sc, RemoteIdentifier);
3848 free_spoolcontrol_struct(&sc);
3853 snprintf(errmsgbuf, n,
3854 "This mailing list only accepts posts from subscribers.");
3855 return (ERROR + NO_SUCH_USER);
3862 if ((CC->user.axlevel < AxProbU)
3863 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3864 snprintf(errmsgbuf, n, "Need to be validated to enter "
3865 "(except in %s> to sysop)", MAILROOM);
3866 return (ERROR + HIGHER_ACCESS_REQUIRED);
3869 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3871 if ( (!(ra & UA_POSTALLOWED)) && (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3873 * To be thorough, we ought to check to see if the message they are
3874 * replying to is actually a valid one in this room, but unless this
3875 * actually becomes a problem we'll go with high performance instead.
3877 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3878 return (ERROR + HIGHER_ACCESS_REQUIRED);
3881 else if (!(ra & UA_POSTALLOWED)) {
3882 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3883 return (ERROR + HIGHER_ACCESS_REQUIRED);
3886 strcpy(errmsgbuf, "Ok");
3892 * Check to see if the specified user has Internet mail permission
3893 * (returns nonzero if permission is granted)
3895 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3897 /* Do not allow twits to send Internet mail */
3898 if (who->axlevel <= AxProbU) return(0);
3900 /* Globally enabled? */
3901 if (config.c_restrict == 0) return(1);
3903 /* User flagged ok? */
3904 if (who->flags & US_INTERNET) return(2);
3906 /* Aide level access? */
3907 if (who->axlevel >= AxAideU) return(3);
3909 /* No mail for you! */
3915 * Validate recipients, count delivery types and errors, and handle aliasing
3916 * FIXME check for dupes!!!!!
3918 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3919 * were specified, or the number of addresses found invalid.
3921 * Caller needs to free the result using free_recipients()
3923 struct recptypes *validate_recipients(const char *supplied_recipients,
3924 const char *RemoteIdentifier,
3926 struct recptypes *ret;
3927 char *recipients = NULL;
3928 char this_recp[256];
3929 char this_recp_cooked[256];
3935 struct ctdluser tempUS;
3936 struct ctdlroom tempQR;
3937 struct ctdlroom tempQR2;
3943 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3944 if (ret == NULL) return(NULL);
3946 /* Set all strings to null and numeric values to zero */
3947 memset(ret, 0, sizeof(struct recptypes));
3949 if (supplied_recipients == NULL) {
3950 recipients = strdup("");
3953 recipients = strdup(supplied_recipients);
3956 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3957 * actually need, but it's healthier for the heap than doing lots of tiny
3958 * realloc() calls instead.
3961 ret->errormsg = malloc(strlen(recipients) + 1024);
3962 ret->recp_local = malloc(strlen(recipients) + 1024);
3963 ret->recp_internet = malloc(strlen(recipients) + 1024);
3964 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3965 ret->recp_room = malloc(strlen(recipients) + 1024);
3966 ret->display_recp = malloc(strlen(recipients) + 1024);
3968 ret->errormsg[0] = 0;
3969 ret->recp_local[0] = 0;
3970 ret->recp_internet[0] = 0;
3971 ret->recp_ignet[0] = 0;
3972 ret->recp_room[0] = 0;
3973 ret->display_recp[0] = 0;
3975 ret->recptypes_magic = RECPTYPES_MAGIC;
3977 /* Change all valid separator characters to commas */
3978 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3979 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3980 recipients[i] = ',';
3984 /* Now start extracting recipients... */
3986 while (!IsEmptyStr(recipients)) {
3988 for (i=0; i<=strlen(recipients); ++i) {
3989 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3990 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3991 safestrncpy(this_recp, recipients, i+1);
3993 if (recipients[i] == ',') {
3994 strcpy(recipients, &recipients[i+1]);
3997 strcpy(recipients, "");
4004 if (IsEmptyStr(this_recp))
4006 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4008 mailtype = alias(this_recp);
4009 mailtype = alias(this_recp);
4010 mailtype = alias(this_recp);
4012 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4013 if (this_recp[j]=='_') {
4014 this_recp_cooked[j] = ' ';
4017 this_recp_cooked[j] = this_recp[j];
4020 this_recp_cooked[j] = '\0';
4025 if (!strcasecmp(this_recp, "sysop")) {
4027 strcpy(this_recp, config.c_aideroom);
4028 if (!IsEmptyStr(ret->recp_room)) {
4029 strcat(ret->recp_room, "|");
4031 strcat(ret->recp_room, this_recp);
4033 else if ( (!strncasecmp(this_recp, "room_", 5))
4034 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4036 /* Save room so we can restore it later */
4040 /* Check permissions to send mail to this room */
4041 err = CtdlDoIHavePermissionToPostInThisRoom(
4046 0 /* 0 = not a reply */
4055 if (!IsEmptyStr(ret->recp_room)) {
4056 strcat(ret->recp_room, "|");
4058 strcat(ret->recp_room, &this_recp_cooked[5]);
4061 /* Restore room in case something needs it */
4065 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4067 strcpy(this_recp, tempUS.fullname);
4068 if (!IsEmptyStr(ret->recp_local)) {
4069 strcat(ret->recp_local, "|");
4071 strcat(ret->recp_local, this_recp);
4073 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4075 strcpy(this_recp, tempUS.fullname);
4076 if (!IsEmptyStr(ret->recp_local)) {
4077 strcat(ret->recp_local, "|");
4079 strcat(ret->recp_local, this_recp);
4087 /* Yes, you're reading this correctly: if the target
4088 * domain points back to the local system or an attached
4089 * Citadel directory, the address is invalid. That's
4090 * because if the address were valid, we would have
4091 * already translated it to a local address by now.
4093 if (IsDirectory(this_recp, 0)) {
4098 ++ret->num_internet;
4099 if (!IsEmptyStr(ret->recp_internet)) {
4100 strcat(ret->recp_internet, "|");
4102 strcat(ret->recp_internet, this_recp);
4107 if (!IsEmptyStr(ret->recp_ignet)) {
4108 strcat(ret->recp_ignet, "|");
4110 strcat(ret->recp_ignet, this_recp);
4118 if (IsEmptyStr(errmsg)) {
4119 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4122 snprintf(append, sizeof append, "%s", errmsg);
4124 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4125 if (!IsEmptyStr(ret->errormsg)) {
4126 strcat(ret->errormsg, "; ");
4128 strcat(ret->errormsg, append);
4132 if (IsEmptyStr(ret->display_recp)) {
4133 strcpy(append, this_recp);
4136 snprintf(append, sizeof append, ", %s", this_recp);
4138 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4139 strcat(ret->display_recp, append);
4144 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4145 ret->num_room + ret->num_error) == 0) {
4146 ret->num_error = (-1);
4147 strcpy(ret->errormsg, "No recipients specified.");
4150 syslog(LOG_DEBUG, "validate_recipients()\n");
4151 syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4152 syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4153 syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4154 syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4155 syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4163 * Destructor for struct recptypes
4165 void free_recipients(struct recptypes *valid) {
4167 if (valid == NULL) {
4171 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4172 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4176 if (valid->errormsg != NULL) free(valid->errormsg);
4177 if (valid->recp_local != NULL) free(valid->recp_local);
4178 if (valid->recp_internet != NULL) free(valid->recp_internet);
4179 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4180 if (valid->recp_room != NULL) free(valid->recp_room);
4181 if (valid->display_recp != NULL) free(valid->display_recp);
4182 if (valid->bounce_to != NULL) free(valid->bounce_to);
4183 if (valid->envelope_from != NULL) free(valid->envelope_from);
4190 * message entry - mode 0 (normal)
4192 void cmd_ent0(char *entargs)
4198 char supplied_euid[128];
4200 int format_type = 0;
4201 char newusername[256];
4202 char newuseremail[256];
4203 struct CtdlMessage *msg;
4207 struct recptypes *valid = NULL;
4208 struct recptypes *valid_to = NULL;
4209 struct recptypes *valid_cc = NULL;
4210 struct recptypes *valid_bcc = NULL;
4212 int subject_required = 0;
4217 int newuseremail_ok = 0;
4218 char references[SIZ];
4223 post = extract_int(entargs, 0);
4224 extract_token(recp, entargs, 1, '|', sizeof recp);
4225 anon_flag = extract_int(entargs, 2);
4226 format_type = extract_int(entargs, 3);
4227 extract_token(subject, entargs, 4, '|', sizeof subject);
4228 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4229 do_confirm = extract_int(entargs, 6);
4230 extract_token(cc, entargs, 7, '|', sizeof cc);
4231 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4232 switch(CC->room.QRdefaultview) {
4235 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4238 supplied_euid[0] = 0;
4241 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4242 extract_token(references, entargs, 11, '|', sizeof references);
4243 for (ptr=references; *ptr != 0; ++ptr) {
4244 if (*ptr == '!') *ptr = '|';
4247 /* first check to make sure the request is valid. */
4249 err = CtdlDoIHavePermissionToPostInThisRoom(
4254 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4258 cprintf("%d %s\n", err, errmsg);
4262 /* Check some other permission type things. */
4264 if (IsEmptyStr(newusername)) {
4265 strcpy(newusername, CC->user.fullname);
4267 if ( (CC->user.axlevel < AxAideU)
4268 && (strcasecmp(newusername, CC->user.fullname))
4269 && (strcasecmp(newusername, CC->cs_inet_fn))
4271 cprintf("%d You don't have permission to author messages as '%s'.\n",
4272 ERROR + HIGHER_ACCESS_REQUIRED,
4279 if (IsEmptyStr(newuseremail)) {
4280 newuseremail_ok = 1;
4283 if (!IsEmptyStr(newuseremail)) {
4284 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4285 newuseremail_ok = 1;
4287 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4288 j = num_tokens(CC->cs_inet_other_emails, '|');
4289 for (i=0; i<j; ++i) {
4290 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4291 if (!strcasecmp(newuseremail, buf)) {
4292 newuseremail_ok = 1;
4298 if (!newuseremail_ok) {
4299 cprintf("%d You don't have permission to author messages as '%s'.\n",
4300 ERROR + HIGHER_ACCESS_REQUIRED,
4306 CC->cs_flags |= CS_POSTING;
4308 /* In mailbox rooms we have to behave a little differently --
4309 * make sure the user has specified at least one recipient. Then
4310 * validate the recipient(s). We do this for the Mail> room, as
4311 * well as any room which has the "Mailbox" view set - unless it
4312 * is the DRAFTS room which does not require recipients
4315 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4316 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4317 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4318 if (CC->user.axlevel < AxProbU) {
4319 strcpy(recp, "sysop");
4324 valid_to = validate_recipients(recp, NULL, 0);
4325 if (valid_to->num_error > 0) {
4326 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4327 free_recipients(valid_to);
4331 valid_cc = validate_recipients(cc, NULL, 0);
4332 if (valid_cc->num_error > 0) {
4333 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4334 free_recipients(valid_to);
4335 free_recipients(valid_cc);
4339 valid_bcc = validate_recipients(bcc, NULL, 0);
4340 if (valid_bcc->num_error > 0) {
4341 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4342 free_recipients(valid_to);
4343 free_recipients(valid_cc);
4344 free_recipients(valid_bcc);
4348 /* Recipient required, but none were specified */
4349 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4350 free_recipients(valid_to);
4351 free_recipients(valid_cc);
4352 free_recipients(valid_bcc);
4353 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4357 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4358 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4359 cprintf("%d You do not have permission "
4360 "to send Internet mail.\n",
4361 ERROR + HIGHER_ACCESS_REQUIRED);
4362 free_recipients(valid_to);
4363 free_recipients(valid_cc);
4364 free_recipients(valid_bcc);
4369 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)
4370 && (CC->user.axlevel < AxNetU) ) {
4371 cprintf("%d Higher access required for network mail.\n",
4372 ERROR + HIGHER_ACCESS_REQUIRED);
4373 free_recipients(valid_to);
4374 free_recipients(valid_cc);
4375 free_recipients(valid_bcc);
4379 if ((RESTRICT_INTERNET == 1)
4380 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4381 && ((CC->user.flags & US_INTERNET) == 0)
4382 && (!CC->internal_pgm)) {
4383 cprintf("%d You don't have access to Internet mail.\n",
4384 ERROR + HIGHER_ACCESS_REQUIRED);
4385 free_recipients(valid_to);
4386 free_recipients(valid_cc);
4387 free_recipients(valid_bcc);
4393 /* Is this a room which has anonymous-only or anonymous-option? */
4394 anonymous = MES_NORMAL;
4395 if (CC->room.QRflags & QR_ANONONLY) {
4396 anonymous = MES_ANONONLY;
4398 if (CC->room.QRflags & QR_ANONOPT) {
4399 if (anon_flag == 1) { /* only if the user requested it */
4400 anonymous = MES_ANONOPT;
4404 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4408 /* Recommend to the client that the use of a message subject is
4409 * strongly recommended in this room, if either the SUBJECTREQ flag
4410 * is set, or if there is one or more Internet email recipients.
4412 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4413 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4414 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4415 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4417 /* If we're only checking the validity of the request, return
4418 * success without creating the message.
4421 cprintf("%d %s|%d\n", CIT_OK,
4422 ((valid_to != NULL) ? valid_to->display_recp : ""),
4424 free_recipients(valid_to);
4425 free_recipients(valid_cc);
4426 free_recipients(valid_bcc);
4430 /* We don't need these anymore because we'll do it differently below */
4431 free_recipients(valid_to);
4432 free_recipients(valid_cc);
4433 free_recipients(valid_bcc);
4435 /* Read in the message from the client. */
4437 cprintf("%d send message\n", START_CHAT_MODE);
4439 cprintf("%d send message\n", SEND_LISTING);
4442 msg = CtdlMakeMessage(&CC->user, recp, cc,
4443 CC->room.QRname, anonymous, format_type,
4444 newusername, newuseremail, subject,
4445 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4448 /* Put together one big recipients struct containing to/cc/bcc all in
4449 * one. This is for the envelope.
4451 char *all_recps = malloc(SIZ * 3);
4452 strcpy(all_recps, recp);
4453 if (!IsEmptyStr(cc)) {
4454 if (!IsEmptyStr(all_recps)) {
4455 strcat(all_recps, ",");
4457 strcat(all_recps, cc);
4459 if (!IsEmptyStr(bcc)) {
4460 if (!IsEmptyStr(all_recps)) {
4461 strcat(all_recps, ",");
4463 strcat(all_recps, bcc);
4465 if (!IsEmptyStr(all_recps)) {
4466 valid = validate_recipients(all_recps, NULL, 0);
4474 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4477 cprintf("%ld\n", msgnum);
4479 cprintf("Message accepted.\n");
4482 cprintf("Internal error.\n");
4484 if (msg->cm_fields['E'] != NULL) {
4485 cprintf("%s\n", msg->cm_fields['E']);
4492 CtdlFreeMessage(msg);
4494 if (valid != NULL) {
4495 free_recipients(valid);
4503 * API function to delete messages which match a set of criteria
4504 * (returns the actual number of messages deleted)
4506 int CtdlDeleteMessages(char *room_name, /* which room */
4507 long *dmsgnums, /* array of msg numbers to be deleted */
4508 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4509 char *content_type /* or "" for any. regular expressions expected. */
4512 struct ctdlroom qrbuf;
4513 struct cdbdata *cdbfr;
4514 long *msglist = NULL;
4515 long *dellist = NULL;
4518 int num_deleted = 0;
4520 struct MetaData smi;
4523 int need_to_free_re = 0;
4525 if (content_type) if (!IsEmptyStr(content_type)) {
4526 regcomp(&re, content_type, 0);
4527 need_to_free_re = 1;
4529 syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4530 room_name, num_dmsgnums, content_type);
4532 /* get room record, obtaining a lock... */
4533 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4534 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4536 if (need_to_free_re) regfree(&re);
4537 return (0); /* room not found */
4539 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4541 if (cdbfr != NULL) {
4542 dellist = malloc(cdbfr->len);
4543 msglist = (long *) cdbfr->ptr;
4544 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4545 num_msgs = cdbfr->len / sizeof(long);
4549 for (i = 0; i < num_msgs; ++i) {
4552 /* Set/clear a bit for each criterion */
4554 /* 0 messages in the list or a null list means that we are
4555 * interested in deleting any messages which meet the other criteria.
4557 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4558 delete_this |= 0x01;
4561 for (j=0; j<num_dmsgnums; ++j) {
4562 if (msglist[i] == dmsgnums[j]) {
4563 delete_this |= 0x01;
4568 if (IsEmptyStr(content_type)) {
4569 delete_this |= 0x02;
4571 GetMetaData(&smi, msglist[i]);
4572 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4573 delete_this |= 0x02;
4577 /* Delete message only if all bits are set */
4578 if (delete_this == 0x03) {
4579 dellist[num_deleted++] = msglist[i];
4584 num_msgs = sort_msglist(msglist, num_msgs);
4585 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4586 msglist, (int)(num_msgs * sizeof(long)));
4589 qrbuf.QRhighest = msglist[num_msgs - 1];
4591 qrbuf.QRhighest = 0;
4593 CtdlPutRoomLock(&qrbuf);
4595 /* Go through the messages we pulled out of the index, and decrement
4596 * their reference counts by 1. If this is the only room the message
4597 * was in, the reference count will reach zero and the message will
4598 * automatically be deleted from the database. We do this in a
4599 * separate pass because there might be plug-in hooks getting called,
4600 * and we don't want that happening during an S_ROOMS critical
4603 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4604 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4605 AdjRefCount(dellist[i], -1);
4608 /* Now free the memory we used, and go away. */
4609 if (msglist != NULL) free(msglist);
4610 if (dellist != NULL) free(dellist);
4611 syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4612 if (need_to_free_re) regfree(&re);
4613 return (num_deleted);
4619 * Check whether the current user has permission to delete messages from
4620 * the current room (returns 1 for yes, 0 for no)
4622 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4624 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4625 if (ra & UA_DELETEALLOWED) return(1);
4633 * Delete message from current room
4635 void cmd_dele(char *args)
4644 extract_token(msgset, args, 0, '|', sizeof msgset);
4645 num_msgs = num_tokens(msgset, ',');
4647 cprintf("%d Nothing to do.\n", CIT_OK);
4651 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4652 cprintf("%d Higher access required.\n",
4653 ERROR + HIGHER_ACCESS_REQUIRED);
4658 * Build our message set to be moved/copied
4660 msgs = malloc(num_msgs * sizeof(long));
4661 for (i=0; i<num_msgs; ++i) {
4662 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4663 msgs[i] = atol(msgtok);
4666 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4670 cprintf("%d %d message%s deleted.\n", CIT_OK,
4671 num_deleted, ((num_deleted != 1) ? "s" : ""));
4673 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4681 * move or copy a message to another room
4683 void cmd_move(char *args)
4690 char targ[ROOMNAMELEN];
4691 struct ctdlroom qtemp;
4698 extract_token(msgset, args, 0, '|', sizeof msgset);
4699 num_msgs = num_tokens(msgset, ',');
4701 cprintf("%d Nothing to do.\n", CIT_OK);
4705 extract_token(targ, args, 1, '|', sizeof targ);
4706 convert_room_name_macros(targ, sizeof targ);
4707 targ[ROOMNAMELEN - 1] = 0;
4708 is_copy = extract_int(args, 2);
4710 if (CtdlGetRoom(&qtemp, targ) != 0) {
4711 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4715 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4716 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4720 CtdlGetUser(&CC->user, CC->curr_user);
4721 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4723 /* Check for permission to perform this operation.
4724 * Remember: "CC->room" is source, "qtemp" is target.
4728 /* Aides can move/copy */
4729 if (CC->user.axlevel >= AxAideU) permit = 1;
4731 /* Room aides can move/copy */
4732 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4734 /* Permit move/copy from personal rooms */
4735 if ((CC->room.QRflags & QR_MAILBOX)
4736 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4738 /* Permit only copy from public to personal room */
4740 && (!(CC->room.QRflags & QR_MAILBOX))
4741 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4743 /* Permit message removal from collaborative delete rooms */
4744 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4746 /* Users allowed to post into the target room may move into it too. */
4747 if ((CC->room.QRflags & QR_MAILBOX) &&
4748 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4750 /* User must have access to target room */
4751 if (!(ra & UA_KNOWN)) permit = 0;
4754 cprintf("%d Higher access required.\n",
4755 ERROR + HIGHER_ACCESS_REQUIRED);
4760 * Build our message set to be moved/copied
4762 msgs = malloc(num_msgs * sizeof(long));
4763 for (i=0; i<num_msgs; ++i) {
4764 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4765 msgs[i] = atol(msgtok);
4771 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4773 cprintf("%d Cannot store message(s) in %s: error %d\n",
4779 /* Now delete the message from the source room,
4780 * if this is a 'move' rather than a 'copy' operation.
4783 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4787 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4793 * GetMetaData() - Get the supplementary record for a message
4795 void GetMetaData(struct MetaData *smibuf, long msgnum)
4798 struct cdbdata *cdbsmi;
4801 memset(smibuf, 0, sizeof(struct MetaData));
4802 smibuf->meta_msgnum = msgnum;
4803 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4805 /* Use the negative of the message number for its supp record index */
4806 TheIndex = (0L - msgnum);
4808 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4809 if (cdbsmi == NULL) {
4810 return; /* record not found; go with defaults */
4812 memcpy(smibuf, cdbsmi->ptr,
4813 ((cdbsmi->len > sizeof(struct MetaData)) ?
4814 sizeof(struct MetaData) : cdbsmi->len));
4821 * PutMetaData() - (re)write supplementary record for a message
4823 void PutMetaData(struct MetaData *smibuf)
4827 /* Use the negative of the message number for the metadata db index */
4828 TheIndex = (0L - smibuf->meta_msgnum);
4830 cdb_store(CDB_MSGMAIN,
4831 &TheIndex, (int)sizeof(long),
4832 smibuf, (int)sizeof(struct MetaData));
4837 * AdjRefCount - submit an adjustment to the reference count for a message.
4838 * (These are just queued -- we actually process them later.)
4840 void AdjRefCount(long msgnum, int incr)
4842 struct arcq new_arcq;
4845 syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4849 begin_critical_section(S_SUPPMSGMAIN);
4850 if (arcfp == NULL) {
4851 arcfp = fopen(file_arcq, "ab+");
4853 end_critical_section(S_SUPPMSGMAIN);
4855 /* msgnum < 0 means that we're trying to close the file */
4857 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4858 begin_critical_section(S_SUPPMSGMAIN);
4859 if (arcfp != NULL) {
4863 end_critical_section(S_SUPPMSGMAIN);
4868 * If we can't open the queue, perform the operation synchronously.
4870 if (arcfp == NULL) {
4871 TDAP_AdjRefCount(msgnum, incr);
4875 new_arcq.arcq_msgnum = msgnum;
4876 new_arcq.arcq_delta = incr;
4877 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4885 * TDAP_ProcessAdjRefCountQueue()
4887 * Process the queue of message count adjustments that was created by calls
4888 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4889 * for each one. This should be an "off hours" operation.
4891 int TDAP_ProcessAdjRefCountQueue(void)
4893 char file_arcq_temp[PATH_MAX];
4896 struct arcq arcq_rec;
4897 int num_records_processed = 0;
4899 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4901 begin_critical_section(S_SUPPMSGMAIN);
4902 if (arcfp != NULL) {
4907 r = link(file_arcq, file_arcq_temp);
4909 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4910 end_critical_section(S_SUPPMSGMAIN);
4911 return(num_records_processed);
4915 end_critical_section(S_SUPPMSGMAIN);
4917 fp = fopen(file_arcq_temp, "rb");
4919 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4920 return(num_records_processed);
4923 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4924 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4925 ++num_records_processed;
4929 r = unlink(file_arcq_temp);
4931 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4934 return(num_records_processed);
4940 * TDAP_AdjRefCount - adjust the reference count for a message.
4941 * This one does it "for real" because it's called by
4942 * the autopurger function that processes the queue
4943 * created by AdjRefCount(). If a message's reference
4944 * count becomes zero, we also delete the message from
4945 * disk and de-index it.
4947 void TDAP_AdjRefCount(long msgnum, int incr)
4950 struct MetaData smi;
4953 /* This is a *tight* critical section; please keep it that way, as
4954 * it may get called while nested in other critical sections.
4955 * Complicating this any further will surely cause deadlock!
4957 begin_critical_section(S_SUPPMSGMAIN);
4958 GetMetaData(&smi, msgnum);
4959 smi.meta_refcount += incr;
4961 end_critical_section(S_SUPPMSGMAIN);
4962 syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4963 msgnum, incr, smi.meta_refcount
4966 /* If the reference count is now zero, delete the message
4967 * (and its supplementary record as well).
4969 if (smi.meta_refcount == 0) {
4970 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
4972 /* Call delete hooks with NULL room to show it has gone altogether */
4973 PerformDeleteHooks(NULL, msgnum);
4975 /* Remove from message base */
4977 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4978 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4980 /* Remove metadata record */
4981 delnum = (0L - msgnum);
4982 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4988 * Write a generic object to this room
4990 * Note: this could be much more efficient. Right now we use two temporary
4991 * files, and still pull the message into memory as with all others.
4993 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4994 char *content_type, /* MIME type of this object */
4995 char *raw_message, /* Data to be written */
4996 off_t raw_length, /* Size of raw_message */
4997 struct ctdluser *is_mailbox, /* Mailbox room? */
4998 int is_binary, /* Is encoding necessary? */
4999 int is_unique, /* Del others of this type? */
5000 unsigned int flags /* Internal save flags */
5004 struct ctdlroom qrbuf;
5005 char roomname[ROOMNAMELEN];
5006 struct CtdlMessage *msg;
5007 char *encoded_message = NULL;
5009 if (is_mailbox != NULL) {
5010 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5013 safestrncpy(roomname, req_room, sizeof(roomname));
5016 syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5019 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5022 encoded_message = malloc((size_t)(raw_length + 4096));
5025 sprintf(encoded_message, "Content-type: %s\n", content_type);
5028 sprintf(&encoded_message[strlen(encoded_message)],
5029 "Content-transfer-encoding: base64\n\n"
5033 sprintf(&encoded_message[strlen(encoded_message)],
5034 "Content-transfer-encoding: 7bit\n\n"
5040 &encoded_message[strlen(encoded_message)],
5048 &encoded_message[strlen(encoded_message)],
5054 syslog(LOG_DEBUG, "Allocating\n");
5055 msg = malloc(sizeof(struct CtdlMessage));
5056 memset(msg, 0, sizeof(struct CtdlMessage));
5057 msg->cm_magic = CTDLMESSAGE_MAGIC;
5058 msg->cm_anon_type = MES_NORMAL;
5059 msg->cm_format_type = 4;
5060 msg->cm_fields['A'] = strdup(CC->user.fullname);
5061 msg->cm_fields['O'] = strdup(req_room);
5062 msg->cm_fields['N'] = strdup(config.c_nodename);
5063 msg->cm_fields['H'] = strdup(config.c_humannode);
5064 msg->cm_flags = flags;
5066 msg->cm_fields['M'] = encoded_message;
5068 /* Create the requested room if we have to. */
5069 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5070 CtdlCreateRoom(roomname,
5071 ( (is_mailbox != NULL) ? 5 : 3 ),
5072 "", 0, 1, 0, VIEW_BBS);
5074 /* If the caller specified this object as unique, delete all
5075 * other objects of this type that are currently in the room.
5078 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5079 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5082 /* Now write the data */
5083 CtdlSubmitMsg(msg, NULL, roomname, 0);
5084 CtdlFreeMessage(msg);
5092 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5093 config_msgnum = msgnum;
5097 char *CtdlGetSysConfig(char *sysconfname) {
5098 char hold_rm[ROOMNAMELEN];
5101 struct CtdlMessage *msg;
5104 strcpy(hold_rm, CC->room.QRname);
5105 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5106 CtdlGetRoom(&CC->room, hold_rm);
5111 /* We want the last (and probably only) config in this room */
5112 begin_critical_section(S_CONFIG);
5113 config_msgnum = (-1L);
5114 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5115 CtdlGetSysConfigBackend, NULL);
5116 msgnum = config_msgnum;
5117 end_critical_section(S_CONFIG);
5123 msg = CtdlFetchMessage(msgnum, 1);
5125 conf = strdup(msg->cm_fields['M']);
5126 CtdlFreeMessage(msg);
5133 CtdlGetRoom(&CC->room, hold_rm);
5135 if (conf != NULL) do {
5136 extract_token(buf, conf, 0, '\n', sizeof buf);
5137 strcpy(conf, &conf[strlen(buf)+1]);
5138 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5144 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5145 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5150 * Determine whether a given Internet address belongs to the current user
5152 int CtdlIsMe(char *addr, int addr_buf_len)
5154 struct recptypes *recp;
5157 recp = validate_recipients(addr, NULL, 0);
5158 if (recp == NULL) return(0);
5160 if (recp->num_local == 0) {
5161 free_recipients(recp);
5165 for (i=0; i<recp->num_local; ++i) {
5166 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5167 if (!strcasecmp(addr, CC->user.fullname)) {
5168 free_recipients(recp);
5173 free_recipients(recp);
5179 * Citadel protocol command to do the same
5181 void cmd_isme(char *argbuf) {
5184 if (CtdlAccessCheck(ac_logged_in)) return;
5185 extract_token(addr, argbuf, 0, '|', sizeof addr);
5187 if (CtdlIsMe(addr, sizeof addr)) {
5188 cprintf("%d %s\n", CIT_OK, addr);
5191 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5197 /*****************************************************************************/
5198 /* MODULE INITIALIZATION STUFF */
5199 /*****************************************************************************/
5201 CTDL_MODULE_INIT(msgbase)
5204 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5205 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5206 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5207 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5208 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5209 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5210 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5211 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5212 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5213 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5214 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5215 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5218 /* return our Subversion id for the Log */