2 * Implements the message store.
4 * Copyright (c) 1987-2011 by the citadel.org team
6 * This program is open source 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 if (need_to_free_re) regfree(&re);
638 return 0; /* No messages at all? No further action. */
641 msglist = (long *) cdbfr->ptr;
642 num_msgs = cdbfr->len / sizeof(long);
644 cdbfr->ptr = NULL; /* clear this so that cdb_free() doesn't free it */
645 cdb_free(cdbfr); /* we own this memory now */
648 * Now begin the traversal.
650 if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
652 /* If the caller is looking for a specific MIME type, filter
653 * out all messages which are not of the type requested.
655 if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
657 /* This call to GetMetaData() sits inside this loop
658 * so that we only do the extra database read per msg
659 * if we need to. Doing the extra read all the time
660 * really kills the server. If we ever need to use
661 * metadata for another search criterion, we need to
662 * move the read somewhere else -- but still be smart
663 * enough to only do the read if the caller has
664 * specified something that will need it.
666 GetMetaData(&smi, msglist[a]);
668 /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
669 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
675 num_msgs = sort_msglist(msglist, num_msgs);
677 /* If a template was supplied, filter out the messages which
678 * don't match. (This could induce some delays!)
681 if (compare != NULL) {
682 for (a = 0; a < num_msgs; ++a) {
683 msg = CtdlFetchMessage(msglist[a], 1);
685 if (CtdlMsgCmp(msg, compare)) {
688 CtdlFreeMessage(msg);
694 /* If a search string was specified, get a message list from
695 * the full text index and remove messages which aren't on both
699 * Since the lists are sorted and strictly ascending, and the
700 * output list is guaranteed to be shorter than or equal to the
701 * input list, we overwrite the bottom of the input list. This
702 * eliminates the need to memmove big chunks of the list over and
705 if ( (num_msgs > 0) && (mode == MSGS_SEARCH) && (search_string) ) {
707 /* Call search module via hook mechanism.
708 * NULL means use any search function available.
709 * otherwise replace with a char * to name of search routine
711 CtdlModuleDoSearch(&num_search_msgs, &search_msgs, search_string, "fulltext");
713 if (num_search_msgs > 0) {
717 orig_num_msgs = num_msgs;
719 for (i=0; i<orig_num_msgs; ++i) {
720 for (j=0; j<num_search_msgs; ++j) {
721 if (msglist[i] == search_msgs[j]) {
722 msglist[num_msgs++] = msglist[i];
728 num_msgs = 0; /* No messages qualify */
730 if (search_msgs != NULL) free(search_msgs);
732 /* Now that we've purged messages which don't contain the search
733 * string, treat a MSGS_SEARCH just like a MSGS_ALL from this
740 * Now iterate through the message list, according to the
741 * criteria supplied by the caller.
744 for (a = 0; a < num_msgs; ++a) {
745 thismsg = msglist[a];
746 if (mode == MSGS_ALL) {
750 is_seen = is_msg_in_sequence_set(
751 vbuf.v_seen, thismsg);
752 if (is_seen) lastold = thismsg;
758 || ((mode == MSGS_OLD) && (is_seen))
759 || ((mode == MSGS_NEW) && (!is_seen))
760 || ((mode == MSGS_LAST) && (a >= (num_msgs - ref)))
761 || ((mode == MSGS_FIRST) && (a < ref))
762 || ((mode == MSGS_GT) && (thismsg > ref))
763 || ((mode == MSGS_LT) && (thismsg < ref))
764 || ((mode == MSGS_EQ) && (thismsg == ref))
767 if ((mode == MSGS_NEW) && (CC->user.flags & US_LASTOLD) && (lastold > 0L) && (printed_lastold == 0) && (!is_seen)) {
769 CallBack(lastold, userdata);
773 if (CallBack) CallBack(thismsg, userdata);
777 if (need_to_free_re) regfree(&re);
780 * We cache the most recent msglist in order to do security checks later
782 if (CC->client_socket > 0) {
783 if (CC->cached_msglist != NULL) {
784 free(CC->cached_msglist);
786 CC->cached_msglist = msglist;
787 CC->cached_num_msgs = num_msgs;
793 return num_processed;
799 * cmd_msgs() - get list of message #'s in this room
800 * implements the MSGS server command using CtdlForEachMessage()
802 void cmd_msgs(char *cmdbuf)
811 int with_template = 0;
812 struct CtdlMessage *template = NULL;
813 char search_string[1024];
814 ForEachMsgCallback CallBack;
816 if (CtdlAccessCheck(ac_logged_in_or_guest)) return;
818 extract_token(which, cmdbuf, 0, '|', sizeof which);
819 cm_ref = extract_int(cmdbuf, 1);
820 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
821 with_template = extract_int(cmdbuf, 2);
822 switch (extract_int(cmdbuf, 3))
826 CallBack = simple_listing;
829 CallBack = headers_listing;
832 CallBack = headers_euid;
837 if (!strncasecmp(which, "OLD", 3))
839 else if (!strncasecmp(which, "NEW", 3))
841 else if (!strncasecmp(which, "FIRST", 5))
843 else if (!strncasecmp(which, "LAST", 4))
845 else if (!strncasecmp(which, "GT", 2))
847 else if (!strncasecmp(which, "LT", 2))
849 else if (!strncasecmp(which, "SEARCH", 6))
854 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
855 cprintf("%d Full text index is not enabled on this server.\n",
856 ERROR + CMD_NOT_SUPPORTED);
862 cprintf("%d Send template then receive message list\n",
864 template = (struct CtdlMessage *)
865 malloc(sizeof(struct CtdlMessage));
866 memset(template, 0, sizeof(struct CtdlMessage));
867 template->cm_magic = CTDLMESSAGE_MAGIC;
868 template->cm_anon_type = MES_NORMAL;
870 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
871 extract_token(tfield, buf, 0, '|', sizeof tfield);
872 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
873 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
874 if (!strcasecmp(tfield, msgkeys[i])) {
875 template->cm_fields[i] =
883 cprintf("%d \n", LISTING_FOLLOWS);
886 CtdlForEachMessage(mode,
887 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
888 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
893 if (template != NULL) CtdlFreeMessage(template);
901 * help_subst() - support routine for help file viewer
903 void help_subst(char *strbuf, char *source, char *dest)
908 while (p = pattern2(strbuf, source), (p >= 0)) {
909 strcpy(workbuf, &strbuf[p + strlen(source)]);
910 strcpy(&strbuf[p], dest);
911 strcat(strbuf, workbuf);
916 void do_help_subst(char *buffer)
920 help_subst(buffer, "^nodename", config.c_nodename);
921 help_subst(buffer, "^humannode", config.c_humannode);
922 help_subst(buffer, "^fqdn", config.c_fqdn);
923 help_subst(buffer, "^username", CC->user.fullname);
924 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
925 help_subst(buffer, "^usernum", buf2);
926 help_subst(buffer, "^sysadm", config.c_sysadm);
927 help_subst(buffer, "^variantname", CITADEL);
928 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
929 help_subst(buffer, "^maxsessions", buf2);
930 help_subst(buffer, "^bbsdir", ctdl_message_dir);
936 * memfmout() - Citadel text formatter and paginator.
937 * Although the original purpose of this routine was to format
938 * text to the reader's screen width, all we're really using it
939 * for here is to format text out to 80 columns before sending it
940 * to the client. The client software may reformat it again.
943 char *mptr, /* where are we going to get our text from? */
944 const char *nl /* string to terminate lines with */
947 unsigned char ch = 0;
954 while (ch=*(mptr++), ch != 0) {
957 if (client_write(outbuf, len) == -1)
959 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
963 if (client_write(nl, nllen) == -1)
965 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
970 else if (ch == '\r') {
971 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
973 else if (isspace(ch)) {
974 if (column > 72) { /* Beyond 72 columns, break on the next space */
975 if (client_write(outbuf, len) == -1)
977 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
981 if (client_write(nl, nllen) == -1)
983 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
996 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
997 if (client_write(outbuf, len) == -1)
999 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1003 if (client_write(nl, nllen) == -1)
1005 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1013 if (client_write(outbuf, len) == -1)
1015 syslog(LOG_ERR, "memfmout(): aborting due to write failure.\n");
1019 client_write(nl, nllen);
1027 * Callback function for mime parser that simply lists the part
1029 void list_this_part(char *name, char *filename, char *partnum, char *disp,
1030 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1031 char *cbid, void *cbuserdata)
1035 ma = (struct ma_info *)cbuserdata;
1036 if (ma->is_ma == 0) {
1037 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1050 * Callback function for multipart prefix
1052 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1053 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1054 char *cbid, void *cbuserdata)
1058 ma = (struct ma_info *)cbuserdata;
1059 if (!strcasecmp(cbtype, "multipart/alternative")) {
1063 if (ma->is_ma == 0) {
1064 cprintf("pref=%s|%s\n", partnum, cbtype);
1069 * Callback function for multipart sufffix
1071 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1072 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1073 char *cbid, void *cbuserdata)
1077 ma = (struct ma_info *)cbuserdata;
1078 if (ma->is_ma == 0) {
1079 cprintf("suff=%s|%s\n", partnum, cbtype);
1081 if (!strcasecmp(cbtype, "multipart/alternative")) {
1088 * Callback function for mime parser that opens a section for downloading
1090 void mime_download(char *name, char *filename, char *partnum, char *disp,
1091 void *content, char *cbtype, char *cbcharset, size_t length,
1092 char *encoding, char *cbid, void *cbuserdata)
1096 /* Silently go away if there's already a download open. */
1097 if (CC->download_fp != NULL)
1101 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1102 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1104 CC->download_fp = tmpfile();
1105 if (CC->download_fp == NULL)
1108 rv = fwrite(content, length, 1, CC->download_fp);
1109 fflush(CC->download_fp);
1110 rewind(CC->download_fp);
1112 OpenCmdResult(filename, cbtype);
1119 * Callback function for mime parser that outputs a section all at once.
1120 * We can specify the desired section by part number *or* content-id.
1122 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1123 void *content, char *cbtype, char *cbcharset, size_t length,
1124 char *encoding, char *cbid, void *cbuserdata)
1126 int *found_it = (int *)cbuserdata;
1129 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1130 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1133 cprintf("%d %d|-1|%s|%s|%s\n",
1140 client_write(content, length);
1146 * Load a message from disk into memory.
1147 * This is used by CtdlOutputMsg() and other fetch functions.
1149 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1150 * using the CtdlMessageFree() function.
1152 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1154 struct cdbdata *dmsgtext;
1155 struct CtdlMessage *ret = NULL;
1159 cit_uint8_t field_header;
1161 syslog(LOG_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1162 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1163 if (dmsgtext == NULL) {
1166 mptr = dmsgtext->ptr;
1167 upper_bound = mptr + dmsgtext->len;
1169 /* Parse the three bytes that begin EVERY message on disk.
1170 * The first is always 0xFF, the on-disk magic number.
1171 * The second is the anonymous/public type byte.
1172 * The third is the format type byte (vari, fixed, or MIME).
1176 syslog(LOG_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1180 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1181 memset(ret, 0, sizeof(struct CtdlMessage));
1183 ret->cm_magic = CTDLMESSAGE_MAGIC;
1184 ret->cm_anon_type = *mptr++; /* Anon type byte */
1185 ret->cm_format_type = *mptr++; /* Format type byte */
1188 * The rest is zero or more arbitrary fields. Load them in.
1189 * We're done when we encounter either a zero-length field or
1190 * have just processed the 'M' (message text) field.
1193 if (mptr >= upper_bound) {
1196 field_header = *mptr++;
1197 ret->cm_fields[field_header] = strdup(mptr);
1199 while (*mptr++ != 0); /* advance to next field */
1201 } while ((mptr < upper_bound) && (field_header != 'M'));
1205 /* Always make sure there's something in the msg text field. If
1206 * it's NULL, the message text is most likely stored separately,
1207 * so go ahead and fetch that. Failing that, just set a dummy
1208 * body so other code doesn't barf.
1210 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1211 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1212 if (dmsgtext != NULL) {
1213 ret->cm_fields['M'] = dmsgtext->ptr;
1214 dmsgtext->ptr = NULL;
1218 if (ret->cm_fields['M'] == NULL) {
1219 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1222 /* Perform "before read" hooks (aborting if any return nonzero) */
1223 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1224 CtdlFreeMessage(ret);
1233 * Returns 1 if the supplied pointer points to a valid Citadel message.
1234 * If the pointer is NULL or the magic number check fails, returns 0.
1236 int is_valid_message(struct CtdlMessage *msg) {
1239 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1240 syslog(LOG_WARNING, "is_valid_message() -- self-check failed\n");
1248 * 'Destructor' for struct CtdlMessage
1250 void CtdlFreeMessage(struct CtdlMessage *msg)
1254 if (is_valid_message(msg) == 0)
1256 if (msg != NULL) free (msg);
1260 for (i = 0; i < 256; ++i)
1261 if (msg->cm_fields[i] != NULL) {
1262 free(msg->cm_fields[i]);
1265 msg->cm_magic = 0; /* just in case */
1271 * Pre callback function for multipart/alternative
1273 * NOTE: this differs from the standard behavior for a reason. Normally when
1274 * displaying multipart/alternative you want to show the _last_ usable
1275 * format in the message. Here we show the _first_ one, because it's
1276 * usually text/plain. Since this set of functions is designed for text
1277 * output to non-MIME-aware clients, this is the desired behavior.
1280 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1281 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1282 char *cbid, void *cbuserdata)
1286 ma = (struct ma_info *)cbuserdata;
1287 syslog(LOG_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1288 if (!strcasecmp(cbtype, "multipart/alternative")) {
1292 if (!strcasecmp(cbtype, "message/rfc822")) {
1298 * Post callback function for multipart/alternative
1300 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1301 void *content, char *cbtype, char *cbcharset, size_t length,
1302 char *encoding, char *cbid, void *cbuserdata)
1306 ma = (struct ma_info *)cbuserdata;
1307 syslog(LOG_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1308 if (!strcasecmp(cbtype, "multipart/alternative")) {
1312 if (!strcasecmp(cbtype, "message/rfc822")) {
1318 * Inline callback function for mime parser that wants to display text
1320 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1321 void *content, char *cbtype, char *cbcharset, size_t length,
1322 char *encoding, char *cbid, void *cbuserdata)
1329 ma = (struct ma_info *)cbuserdata;
1332 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1333 partnum, filename, cbtype, (long)length);
1336 * If we're in the middle of a multipart/alternative scope and
1337 * we've already printed another section, skip this one.
1339 if ( (ma->is_ma) && (ma->did_print) ) {
1340 syslog(LOG_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1345 if ( (!strcasecmp(cbtype, "text/plain"))
1346 || (IsEmptyStr(cbtype)) ) {
1349 client_write(wptr, length);
1350 if (wptr[length-1] != '\n') {
1357 if (!strcasecmp(cbtype, "text/html")) {
1358 ptr = html_to_ascii(content, length, 80, 0);
1360 client_write(ptr, wlen);
1361 if (ptr[wlen-1] != '\n') {
1368 if (ma->use_fo_hooks) {
1369 if (PerformFixedOutputHooks(cbtype, content, length)) {
1370 /* above function returns nonzero if it handled the part */
1375 if (strncasecmp(cbtype, "multipart/", 10)) {
1376 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1377 partnum, filename, cbtype, (long)length);
1383 * The client is elegant and sophisticated and wants to be choosy about
1384 * MIME content types, so figure out which multipart/alternative part
1385 * we're going to send.
1387 * We use a system of weights. When we find a part that matches one of the
1388 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1389 * and then set ma->chosen_pref to that MIME type's position in our preference
1390 * list. If we then hit another match, we only replace the first match if
1391 * the preference value is lower.
1393 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1394 void *content, char *cbtype, char *cbcharset, size_t length,
1395 char *encoding, char *cbid, void *cbuserdata)
1401 ma = (struct ma_info *)cbuserdata;
1403 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1404 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1405 // I don't know if there are any side effects! Please TEST TEST TEST
1406 //if (ma->is_ma > 0) {
1408 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1409 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1410 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1411 if (i < ma->chosen_pref) {
1412 syslog(LOG_DEBUG, "Setting chosen part: <%s>\n", partnum);
1413 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1414 ma->chosen_pref = i;
1421 * Now that we've chosen our preferred part, output it.
1423 void output_preferred(char *name,
1437 int add_newline = 0;
1440 char *decoded = NULL;
1441 size_t bytes_decoded;
1444 ma = (struct ma_info *)cbuserdata;
1446 /* This is not the MIME part you're looking for... */
1447 if (strcasecmp(partnum, ma->chosen_part)) return;
1449 /* If the content-type of this part is in our preferred formats
1450 * list, we can simply output it verbatim.
1452 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1453 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1454 if (!strcasecmp(buf, cbtype)) {
1455 /* Yeah! Go! W00t!! */
1456 if (ma->dont_decode == 0)
1457 rc = mime_decode_now (content,
1463 break; /* Give us the chance, maybe theres another one. */
1465 if (rc == 0) text_content = (char *)content;
1467 text_content = decoded;
1468 length = bytes_decoded;
1471 if (text_content[length-1] != '\n') {
1474 cprintf("Content-type: %s", cbtype);
1475 if (!IsEmptyStr(cbcharset)) {
1476 cprintf("; charset=%s", cbcharset);
1478 cprintf("\nContent-length: %d\n",
1479 (int)(length + add_newline) );
1480 if (!IsEmptyStr(encoding)) {
1481 cprintf("Content-transfer-encoding: %s\n", encoding);
1484 cprintf("Content-transfer-encoding: 7bit\n");
1486 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1488 if (client_write(text_content, length) == -1)
1490 syslog(LOG_ERR, "output_preferred(): aborting due to write failure.\n");
1493 if (add_newline) cprintf("\n");
1494 if (decoded != NULL) free(decoded);
1499 /* No translations required or possible: output as text/plain */
1500 cprintf("Content-type: text/plain\n\n");
1502 if (ma->dont_decode == 0)
1503 rc = mime_decode_now (content,
1509 return; /* Give us the chance, maybe theres another one. */
1511 if (rc == 0) text_content = (char *)content;
1513 text_content = decoded;
1514 length = bytes_decoded;
1517 fixed_output(name, filename, partnum, disp, text_content, cbtype, cbcharset,
1518 length, encoding, cbid, cbuserdata);
1519 if (decoded != NULL) free(decoded);
1524 char desired_section[64];
1531 * Callback function for
1533 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1534 void *content, char *cbtype, char *cbcharset, size_t length,
1535 char *encoding, char *cbid, void *cbuserdata)
1537 struct encapmsg *encap;
1539 encap = (struct encapmsg *)cbuserdata;
1541 /* Only proceed if this is the desired section... */
1542 if (!strcasecmp(encap->desired_section, partnum)) {
1543 encap->msglen = length;
1544 encap->msg = malloc(length + 2);
1545 memcpy(encap->msg, content, length);
1552 * Determine whether the specified message exists in the cached_msglist
1553 * (This is a security check)
1555 int check_cached_msglist(long msgnum) {
1557 /* cases in which we skip the check */
1558 if (!CC) return om_ok; /* not a session */
1559 if (CC->client_socket <= 0) return om_ok; /* not a client session */
1560 if (CC->cached_msglist == NULL) return om_access_denied; /* no msglist fetched */
1561 if (CC->cached_num_msgs == 0) return om_access_denied; /* nothing to check */
1564 /* Do a binary search within the cached_msglist for the requested msgnum */
1566 int max = (CC->cached_num_msgs - 1);
1568 while (max >= min) {
1569 int middle = min + (max-min) / 2 ;
1570 if (msgnum == CC->cached_msglist[middle]) {
1573 if (msgnum > CC->cached_msglist[middle]) {
1581 return om_access_denied;
1586 * Determine whether the currently logged in session has permission to read
1587 * messages in the current room.
1589 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1590 if ( (!(CC->logged_in))
1591 && (!(CC->internal_pgm))
1592 && (!config.c_guest_logins)
1594 return(om_not_logged_in);
1601 * Get a message off disk. (returns om_* values found in msgbase.h)
1604 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1605 int mode, /* how would you like that message? */
1606 int headers_only, /* eschew the message body? */
1607 int do_proto, /* do Citadel protocol responses? */
1608 int crlf, /* Use CRLF newlines instead of LF? */
1609 char *section, /* NULL or a message/rfc822 section */
1610 int flags /* various flags; see msgbase.h */
1612 struct CtdlMessage *TheMessage = NULL;
1613 int retcode = om_no_such_msg;
1614 struct encapmsg encap;
1617 syslog(LOG_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1619 (section ? section : "<>")
1622 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1625 if (r == om_not_logged_in) {
1626 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1629 cprintf("%d An unknown error has occurred.\n", ERROR);
1636 * Check to make sure the message is actually IN this room
1638 r = check_cached_msglist(msg_num);
1639 if (r == om_access_denied) {
1640 /* Not in the cache? We get ONE shot to check it again. */
1641 CtdlForEachMessage(MSGS_ALL, 0L, NULL, NULL, NULL, NULL, NULL);
1642 r = check_cached_msglist(msg_num);
1645 syslog(LOG_DEBUG, "Security check fail: message %ld is not in %s\n",
1646 msg_num, CC->room.QRname
1649 if (r == om_access_denied) {
1650 cprintf("%d message %ld was not found in this room\n",
1651 ERROR + HIGHER_ACCESS_REQUIRED,
1660 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1661 * request that we don't even bother loading the body into memory.
1663 if (headers_only == HEADERS_FAST) {
1664 TheMessage = CtdlFetchMessage(msg_num, 0);
1667 TheMessage = CtdlFetchMessage(msg_num, 1);
1670 if (TheMessage == NULL) {
1671 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1672 ERROR + MESSAGE_NOT_FOUND, msg_num);
1673 return(om_no_such_msg);
1676 /* Here is the weird form of this command, to process only an
1677 * encapsulated message/rfc822 section.
1679 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1680 memset(&encap, 0, sizeof encap);
1681 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1682 mime_parser(TheMessage->cm_fields['M'],
1684 *extract_encapsulated_message,
1685 NULL, NULL, (void *)&encap, 0
1687 CtdlFreeMessage(TheMessage);
1691 encap.msg[encap.msglen] = 0;
1692 TheMessage = convert_internet_message(encap.msg);
1693 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1695 /* Now we let it fall through to the bottom of this
1696 * function, because TheMessage now contains the
1697 * encapsulated message instead of the top-level
1698 * message. Isn't that neat?
1703 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1704 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1705 retcode = om_no_such_msg;
1710 /* Ok, output the message now */
1711 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1712 CtdlFreeMessage(TheMessage);
1718 char *qp_encode_email_addrs(char *source)
1720 char *user, *node, *name;
1721 const char headerStr[] = "=?UTF-8?Q?";
1725 int need_to_encode = 0;
1731 long nAddrPtrMax = 50;
1736 if (source == NULL) return source;
1737 if (IsEmptyStr(source)) return source;
1739 syslog(LOG_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1741 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1742 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1743 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1746 while (!IsEmptyStr (&source[i])) {
1747 if (nColons >= nAddrPtrMax){
1750 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1751 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1752 free (AddrPtr), AddrPtr = ptr;
1754 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1755 memset(&ptr[nAddrPtrMax], 0,
1756 sizeof (long) * nAddrPtrMax);
1758 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1759 free (AddrUtf8), AddrUtf8 = ptr;
1762 if (((unsigned char) source[i] < 32) ||
1763 ((unsigned char) source[i] > 126)) {
1765 AddrUtf8[nColons] = 1;
1767 if (source[i] == '"')
1768 InQuotes = !InQuotes;
1769 if (!InQuotes && source[i] == ',') {
1770 AddrPtr[nColons] = i;
1775 if (need_to_encode == 0) {
1782 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1783 Encoded = (char*) malloc (EncodedMaxLen);
1785 for (i = 0; i < nColons; i++)
1786 source[AddrPtr[i]++] = '\0';
1787 /* TODO: if libidn, this might get larger*/
1788 user = malloc(SourceLen + 1);
1789 node = malloc(SourceLen + 1);
1790 name = malloc(SourceLen + 1);
1794 for (i = 0; i < nColons && nPtr != NULL; i++) {
1795 nmax = EncodedMaxLen - (nPtr - Encoded);
1797 process_rfc822_addr(&source[AddrPtr[i]],
1801 /* TODO: libIDN here ! */
1802 if (IsEmptyStr(name)) {
1803 n = snprintf(nPtr, nmax,
1804 (i==0)?"%s@%s" : ",%s@%s",
1808 EncodedName = rfc2047encode(name, strlen(name));
1809 n = snprintf(nPtr, nmax,
1810 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1811 EncodedName, user, node);
1816 n = snprintf(nPtr, nmax,
1817 (i==0)?"%s" : ",%s",
1818 &source[AddrPtr[i]]);
1824 ptr = (char*) malloc(EncodedMaxLen * 2);
1825 memcpy(ptr, Encoded, EncodedMaxLen);
1826 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1827 free(Encoded), Encoded = ptr;
1829 i--; /* do it once more with properly lengthened buffer */
1832 for (i = 0; i < nColons; i++)
1833 source[--AddrPtr[i]] = ',';
1844 /* If the last item in a list of recipients was truncated to a partial address,
1845 * remove it completely in order to avoid choking libSieve
1847 void sanitize_truncated_recipient(char *str)
1850 if (num_tokens(str, ',') < 2) return;
1852 int len = strlen(str);
1853 if (len < 900) return;
1854 if (len > 998) str[998] = 0;
1856 char *cptr = strrchr(str, ',');
1859 char *lptr = strchr(cptr, '<');
1860 char *rptr = strchr(cptr, '>');
1862 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1868 void OutputCtdlMsgHeaders(
1869 struct CtdlMessage *TheMessage,
1870 int do_proto) /* do Citadel protocol responses? */
1876 char display_name[256];
1878 /* begin header processing loop for Citadel message format */
1879 safestrncpy(display_name, "<unknown>", sizeof display_name);
1880 if (TheMessage->cm_fields['A']) {
1881 strcpy(buf, TheMessage->cm_fields['A']);
1882 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1883 safestrncpy(display_name, "****", sizeof display_name);
1885 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1886 safestrncpy(display_name, "anonymous", sizeof display_name);
1889 safestrncpy(display_name, buf, sizeof display_name);
1891 if ((is_room_aide())
1892 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1893 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1894 size_t tmp = strlen(display_name);
1895 snprintf(&display_name[tmp],
1896 sizeof display_name - tmp,
1901 /* Don't show Internet address for users on the
1902 * local Citadel network.
1905 if (TheMessage->cm_fields['N'] != NULL)
1906 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1907 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1911 /* Now spew the header fields in the order we like them. */
1912 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1913 for (i=0; i<n; ++i) {
1914 k = (int) allkeys[i];
1916 if ( (TheMessage->cm_fields[k] != NULL)
1917 && (msgkeys[k] != NULL) ) {
1918 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1919 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1922 if (do_proto) cprintf("%s=%s\n",
1926 else if ((k == 'F') && (suppress_f)) {
1929 /* Masquerade display name if needed */
1931 if (do_proto) cprintf("%s=%s\n",
1933 TheMessage->cm_fields[k]
1942 void OutputRFC822MsgHeaders(
1943 struct CtdlMessage *TheMessage,
1944 int flags, /* should the bessage be exported clean */
1946 char *mid, long sizeof_mid,
1947 char *suser, long sizeof_suser,
1948 char *luser, long sizeof_luser,
1949 char *fuser, long sizeof_fuser,
1950 char *snode, long sizeof_snode)
1952 char datestamp[100];
1953 int subject_found = 0;
1960 for (i = 0; i < 256; ++i) {
1961 if (TheMessage->cm_fields[i]) {
1962 mptr = mpptr = TheMessage->cm_fields[i];
1965 safestrncpy(luser, mptr, sizeof_luser);
1966 safestrncpy(suser, mptr, sizeof_suser);
1968 else if (i == 'Y') {
1969 if ((flags & QP_EADDR) != 0) {
1970 mptr = qp_encode_email_addrs(mptr);
1972 sanitize_truncated_recipient(mptr);
1973 cprintf("CC: %s%s", mptr, nl);
1975 else if (i == 'P') {
1976 cprintf("Return-Path: %s%s", mptr, nl);
1978 else if (i == 'L') {
1979 cprintf("List-ID: %s%s", mptr, nl);
1981 else if (i == 'V') {
1982 if ((flags & QP_EADDR) != 0)
1983 mptr = qp_encode_email_addrs(mptr);
1985 while ((*hptr != '\0') && isspace(*hptr))
1987 if (!IsEmptyStr(hptr))
1988 cprintf("Envelope-To: %s%s", hptr, nl);
1990 else if (i == 'U') {
1991 cprintf("Subject: %s%s", mptr, nl);
1995 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1997 safestrncpy(fuser, mptr, sizeof_fuser);
1998 /* else if (i == 'O')
1999 cprintf("X-Citadel-Room: %s%s",
2002 safestrncpy(snode, mptr, sizeof_snode);
2005 if (haschar(mptr, '@') == 0)
2007 sanitize_truncated_recipient(mptr);
2008 cprintf("To: %s@%s", mptr, config.c_fqdn);
2013 if ((flags & QP_EADDR) != 0) {
2014 mptr = qp_encode_email_addrs(mptr);
2016 sanitize_truncated_recipient(mptr);
2017 cprintf("To: %s", mptr);
2021 else if (i == 'T') {
2022 datestring(datestamp, sizeof datestamp,
2023 atol(mptr), DATESTRING_RFC822);
2024 cprintf("Date: %s%s", datestamp, nl);
2026 else if (i == 'W') {
2027 cprintf("References: ");
2028 k = num_tokens(mptr, '|');
2029 for (j=0; j<k; ++j) {
2030 extract_token(buf, mptr, j, '|', sizeof buf);
2031 cprintf("<%s>", buf);
2040 else if (i == 'K') {
2041 cprintf("Reply-To: <%s>%s", mptr, nl);
2047 if (subject_found == 0) {
2048 cprintf("Subject: (no subject)%s", nl);
2053 void Dump_RFC822HeadersBody(
2054 struct CtdlMessage *TheMessage,
2055 int headers_only, /* eschew the message body? */
2056 int flags, /* should the bessage be exported clean? */
2060 cit_uint8_t prev_ch;
2062 const char *StartOfText = StrBufNOTNULL;
2065 int nllen = strlen(nl);
2069 mptr = TheMessage->cm_fields['M'];
2073 while (*mptr != '\0') {
2074 if (*mptr == '\r') {
2081 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
2083 eoh = *(mptr+1) == '\n';
2087 StartOfText = strchr(StartOfText, '\n');
2088 StartOfText = strchr(StartOfText, '\n');
2091 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
2092 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
2093 ((headers_only != HEADERS_NONE) &&
2094 (headers_only != HEADERS_ONLY))
2096 if (*mptr == '\n') {
2097 memcpy(&outbuf[outlen], nl, nllen);
2099 outbuf[outlen] = '\0';
2102 outbuf[outlen++] = *mptr;
2106 if (flags & ESC_DOT)
2108 if ((prev_ch == '\n') &&
2110 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
2112 outbuf[outlen++] = '.';
2117 if (outlen > 1000) {
2118 if (client_write(outbuf, outlen) == -1)
2120 syslog(LOG_ERR, "Dump_RFC822HeadersBody(): aborting due to write failure.\n");
2127 rc = client_write(outbuf, outlen);
2134 /* If the format type on disk is 1 (fixed-format), then we want
2135 * everything to be output completely literally ... regardless of
2136 * what message transfer format is in use.
2138 void DumpFormatFixed(
2139 struct CtdlMessage *TheMessage,
2140 int mode, /* how would you like that message? */
2147 int nllen = strlen (nl);
2150 mptr = TheMessage->cm_fields['M'];
2152 if (mode == MT_MIME) {
2153 cprintf("Content-type: text/plain\n\n");
2157 while (ch = *mptr++, ch > 0) {
2161 if ((buflen > 250) && (!xlline)){
2165 while ((buflen > 0) &&
2166 (!isspace(buf[buflen])))
2172 mptr -= tbuflen - buflen;
2177 /* if we reach the outer bounds of our buffer,
2178 abort without respect what whe purge. */
2181 (buflen > SIZ - nllen - 2)))
2185 memcpy (&buf[buflen], nl, nllen);
2189 if (client_write(buf, buflen) == -1)
2191 syslog(LOG_ERR, "DumpFormatFixed(): aborting due to write failure.\n");
2203 if (!IsEmptyStr(buf))
2204 cprintf("%s%s", buf, nl);
2208 * Get a message off disk. (returns om_* values found in msgbase.h)
2210 int CtdlOutputPreLoadedMsg(
2211 struct CtdlMessage *TheMessage,
2212 int mode, /* how would you like that message? */
2213 int headers_only, /* eschew the message body? */
2214 int do_proto, /* do Citadel protocol responses? */
2215 int crlf, /* Use CRLF newlines instead of LF? */
2216 int flags /* should the bessage be exported clean? */
2220 const char *nl; /* newline string */
2223 /* Buffers needed for RFC822 translation. These are all filled
2224 * using functions that are bounds-checked, and therefore we can
2225 * make them substantially smaller than SIZ.
2233 syslog(LOG_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2234 ((TheMessage == NULL) ? "NULL" : "not null"),
2235 mode, headers_only, do_proto, crlf);
2237 strcpy(mid, "unknown");
2238 nl = (crlf ? "\r\n" : "\n");
2240 if (!is_valid_message(TheMessage)) {
2242 "ERROR: invalid preloaded message for output\n");
2244 return(om_no_such_msg);
2247 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2248 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2250 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2251 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2254 /* Are we downloading a MIME component? */
2255 if (mode == MT_DOWNLOAD) {
2256 if (TheMessage->cm_format_type != FMT_RFC822) {
2258 cprintf("%d This is not a MIME message.\n",
2259 ERROR + ILLEGAL_VALUE);
2260 } else if (CC->download_fp != NULL) {
2261 if (do_proto) cprintf(
2262 "%d You already have a download open.\n",
2263 ERROR + RESOURCE_BUSY);
2265 /* Parse the message text component */
2266 mptr = TheMessage->cm_fields['M'];
2267 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2268 /* If there's no file open by this time, the requested
2269 * section wasn't found, so print an error
2271 if (CC->download_fp == NULL) {
2272 if (do_proto) cprintf(
2273 "%d Section %s not found.\n",
2274 ERROR + FILE_NOT_FOUND,
2275 CC->download_desired_section);
2278 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2281 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2282 * in a single server operation instead of opening a download file.
2284 if (mode == MT_SPEW_SECTION) {
2285 if (TheMessage->cm_format_type != FMT_RFC822) {
2287 cprintf("%d This is not a MIME message.\n",
2288 ERROR + ILLEGAL_VALUE);
2290 /* Parse the message text component */
2293 mptr = TheMessage->cm_fields['M'];
2294 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2295 /* If section wasn't found, print an error
2298 if (do_proto) cprintf(
2299 "%d Section %s not found.\n",
2300 ERROR + FILE_NOT_FOUND,
2301 CC->download_desired_section);
2304 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2307 /* now for the user-mode message reading loops */
2308 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2310 /* Does the caller want to skip the headers? */
2311 if (headers_only == HEADERS_NONE) goto START_TEXT;
2313 /* Tell the client which format type we're using. */
2314 if ( (mode == MT_CITADEL) && (do_proto) ) {
2315 cprintf("type=%d\n", TheMessage->cm_format_type);
2318 /* nhdr=yes means that we're only displaying headers, no body */
2319 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2320 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2323 cprintf("nhdr=yes\n");
2326 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2327 OutputCtdlMsgHeaders(TheMessage, do_proto);
2330 /* begin header processing loop for RFC822 transfer format */
2334 strcpy(snode, NODENAME);
2335 if (mode == MT_RFC822)
2336 OutputRFC822MsgHeaders(
2341 suser, sizeof(suser),
2342 luser, sizeof(luser),
2343 fuser, sizeof(fuser),
2344 snode, sizeof(snode)
2348 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2349 suser[i] = tolower(suser[i]);
2350 if (!isalnum(suser[i])) suser[i]='_';
2353 if (mode == MT_RFC822) {
2354 if (!strcasecmp(snode, NODENAME)) {
2355 safestrncpy(snode, FQDN, sizeof snode);
2358 /* Construct a fun message id */
2359 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2360 if (strchr(mid, '@')==NULL) {
2361 cprintf("@%s", snode);
2365 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2366 cprintf("From: \"----\" <x@x.org>%s", nl);
2368 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2369 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2371 else if (!IsEmptyStr(fuser)) {
2372 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2375 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2378 /* Blank line signifying RFC822 end-of-headers */
2379 if (TheMessage->cm_format_type != FMT_RFC822) {
2384 /* end header processing loop ... at this point, we're in the text */
2386 if (headers_only == HEADERS_FAST) goto DONE;
2388 /* Tell the client about the MIME parts in this message */
2389 if (TheMessage->cm_format_type == FMT_RFC822) {
2390 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2391 mptr = TheMessage->cm_fields['M'];
2392 memset(&ma, 0, sizeof(struct ma_info));
2393 mime_parser(mptr, NULL,
2394 (do_proto ? *list_this_part : NULL),
2395 (do_proto ? *list_this_pref : NULL),
2396 (do_proto ? *list_this_suff : NULL),
2399 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2400 Dump_RFC822HeadersBody(
2409 if (headers_only == HEADERS_ONLY) {
2413 /* signify start of msg text */
2414 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2415 if (do_proto) cprintf("text\n");
2418 if (TheMessage->cm_format_type == FMT_FIXED)
2421 mode, /* how would you like that message? */
2424 /* If the message on disk is format 0 (Citadel vari-format), we
2425 * output using the formatter at 80 columns. This is the final output
2426 * form if the transfer format is RFC822, but if the transfer format
2427 * is Citadel proprietary, it'll still work, because the indentation
2428 * for new paragraphs is correct and the client will reformat the
2429 * message to the reader's screen width.
2431 if (TheMessage->cm_format_type == FMT_CITADEL) {
2432 mptr = TheMessage->cm_fields['M'];
2434 if (mode == MT_MIME) {
2435 cprintf("Content-type: text/x-citadel-variformat\n\n");
2440 /* If the message on disk is format 4 (MIME), we've gotta hand it
2441 * off to the MIME parser. The client has already been told that
2442 * this message is format 1 (fixed format), so the callback function
2443 * we use will display those parts as-is.
2445 if (TheMessage->cm_format_type == FMT_RFC822) {
2446 memset(&ma, 0, sizeof(struct ma_info));
2448 if (mode == MT_MIME) {
2449 ma.use_fo_hooks = 0;
2450 strcpy(ma.chosen_part, "1");
2451 ma.chosen_pref = 9999;
2452 ma.dont_decode = CC->msg4_dont_decode;
2453 mime_parser(mptr, NULL,
2454 *choose_preferred, *fixed_output_pre,
2455 *fixed_output_post, (void *)&ma, 1);
2456 mime_parser(mptr, NULL,
2457 *output_preferred, NULL, NULL, (void *)&ma, 1);
2460 ma.use_fo_hooks = 1;
2461 mime_parser(mptr, NULL,
2462 *fixed_output, *fixed_output_pre,
2463 *fixed_output_post, (void *)&ma, 0);
2468 DONE: /* now we're done */
2469 if (do_proto) cprintf("000\n");
2475 * display a message (mode 0 - Citadel proprietary)
2477 void cmd_msg0(char *cmdbuf)
2480 int headers_only = HEADERS_ALL;
2482 msgid = extract_long(cmdbuf, 0);
2483 headers_only = extract_int(cmdbuf, 1);
2485 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2491 * display a message (mode 2 - RFC822)
2493 void cmd_msg2(char *cmdbuf)
2496 int headers_only = HEADERS_ALL;
2498 msgid = extract_long(cmdbuf, 0);
2499 headers_only = extract_int(cmdbuf, 1);
2501 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2507 * display a message (mode 3 - IGnet raw format - internal programs only)
2509 void cmd_msg3(char *cmdbuf)
2512 struct CtdlMessage *msg = NULL;
2515 if (CC->internal_pgm == 0) {
2516 cprintf("%d This command is for internal programs only.\n",
2517 ERROR + HIGHER_ACCESS_REQUIRED);
2521 msgnum = extract_long(cmdbuf, 0);
2522 msg = CtdlFetchMessage(msgnum, 1);
2524 cprintf("%d Message %ld not found.\n",
2525 ERROR + MESSAGE_NOT_FOUND, msgnum);
2529 serialize_message(&smr, msg);
2530 CtdlFreeMessage(msg);
2533 cprintf("%d Unable to serialize message\n",
2534 ERROR + INTERNAL_ERROR);
2538 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2539 client_write((char *)smr.ser, (int)smr.len);
2546 * Display a message using MIME content types
2548 void cmd_msg4(char *cmdbuf)
2553 msgid = extract_long(cmdbuf, 0);
2554 extract_token(section, cmdbuf, 1, '|', sizeof section);
2555 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2561 * Client tells us its preferred message format(s)
2563 void cmd_msgp(char *cmdbuf)
2565 if (!strcasecmp(cmdbuf, "dont_decode")) {
2566 CC->msg4_dont_decode = 1;
2567 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2570 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2571 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2577 * Open a component of a MIME message as a download file
2579 void cmd_opna(char *cmdbuf)
2582 char desired_section[128];
2584 msgid = extract_long(cmdbuf, 0);
2585 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2586 safestrncpy(CC->download_desired_section, desired_section,
2587 sizeof CC->download_desired_section);
2588 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2593 * Open a component of a MIME message and transmit it all at once
2595 void cmd_dlat(char *cmdbuf)
2598 char desired_section[128];
2600 msgid = extract_long(cmdbuf, 0);
2601 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2602 safestrncpy(CC->download_desired_section, desired_section,
2603 sizeof CC->download_desired_section);
2604 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2609 * Save one or more message pointers into a specified room
2610 * (Returns 0 for success, nonzero for failure)
2611 * roomname may be NULL to use the current room
2613 * Note that the 'supplied_msg' field may be set to NULL, in which case
2614 * the message will be fetched from disk, by number, if we need to perform
2615 * replication checks. This adds an additional database read, so if the
2616 * caller already has the message in memory then it should be supplied. (Obviously
2617 * this mode of operation only works if we're saving a single message.)
2619 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2620 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2623 char hold_rm[ROOMNAMELEN];
2624 struct cdbdata *cdbfr;
2627 long highest_msg = 0L;
2630 struct CtdlMessage *msg = NULL;
2632 long *msgs_to_be_merged = NULL;
2633 int num_msgs_to_be_merged = 0;
2636 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2637 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2640 strcpy(hold_rm, CC->room.QRname);
2643 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2644 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2645 if (num_newmsgs > 1) supplied_msg = NULL;
2647 /* Now the regular stuff */
2648 if (CtdlGetRoomLock(&CC->room,
2649 ((roomname != NULL) ? roomname : CC->room.QRname) )
2651 syslog(LOG_ERR, "No such room <%s>\n", roomname);
2652 return(ERROR + ROOM_NOT_FOUND);
2656 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2657 num_msgs_to_be_merged = 0;
2660 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2661 if (cdbfr == NULL) {
2665 msglist = (long *) cdbfr->ptr;
2666 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2667 num_msgs = cdbfr->len / sizeof(long);
2672 /* Create a list of msgid's which were supplied by the caller, but do
2673 * not already exist in the target room. It is absolutely taboo to
2674 * have more than one reference to the same message in a room.
2676 for (i=0; i<num_newmsgs; ++i) {
2678 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2679 if (msglist[j] == newmsgidlist[i]) {
2684 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2688 syslog(LOG_DEBUG, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2691 * Now merge the new messages
2693 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2694 if (msglist == NULL) {
2695 syslog(LOG_ALERT, "ERROR: can't realloc message list!\n");
2697 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2698 num_msgs += num_msgs_to_be_merged;
2700 /* Sort the message list, so all the msgid's are in order */
2701 num_msgs = sort_msglist(msglist, num_msgs);
2703 /* Determine the highest message number */
2704 highest_msg = msglist[num_msgs - 1];
2706 /* Write it back to disk. */
2707 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2708 msglist, (int)(num_msgs * sizeof(long)));
2710 /* Free up the memory we used. */
2713 /* Update the highest-message pointer and unlock the room. */
2714 CC->room.QRhighest = highest_msg;
2715 CtdlPutRoomLock(&CC->room);
2717 /* Perform replication checks if necessary */
2718 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2719 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2721 for (i=0; i<num_msgs_to_be_merged; ++i) {
2722 msgid = msgs_to_be_merged[i];
2724 if (supplied_msg != NULL) {
2728 msg = CtdlFetchMessage(msgid, 0);
2732 ReplicationChecks(msg);
2734 /* If the message has an Exclusive ID, index that... */
2735 if (msg->cm_fields['E'] != NULL) {
2736 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2739 /* Free up the memory we may have allocated */
2740 if (msg != supplied_msg) {
2741 CtdlFreeMessage(msg);
2749 syslog(LOG_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2752 /* Submit this room for processing by hooks */
2753 PerformRoomHooks(&CC->room);
2755 /* Go back to the room we were in before we wandered here... */
2756 CtdlGetRoom(&CC->room, hold_rm);
2758 /* Bump the reference count for all messages which were merged */
2759 if (!suppress_refcount_adj) {
2760 for (i=0; i<num_msgs_to_be_merged; ++i) {
2761 AdjRefCount(msgs_to_be_merged[i], +1);
2765 /* Free up memory... */
2766 if (msgs_to_be_merged != NULL) {
2767 free(msgs_to_be_merged);
2770 /* Return success. */
2776 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2779 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2780 int do_repl_check, struct CtdlMessage *supplied_msg)
2782 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2789 * Message base operation to save a new message to the message store
2790 * (returns new message number)
2792 * This is the back end for CtdlSubmitMsg() and should not be directly
2793 * called by server-side modules.
2796 long send_message(struct CtdlMessage *msg) {
2804 /* Get a new message number */
2805 newmsgid = get_new_message_number();
2806 snprintf(msgidbuf, sizeof msgidbuf, "%08lX-%08lX@%s",
2807 (long unsigned int) time(NULL),
2808 (long unsigned int) newmsgid,
2812 /* Generate an ID if we don't have one already */
2813 if (msg->cm_fields['I']==NULL) {
2814 msg->cm_fields['I'] = strdup(msgidbuf);
2817 /* If the message is big, set its body aside for storage elsewhere */
2818 if (msg->cm_fields['M'] != NULL) {
2819 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2821 holdM = msg->cm_fields['M'];
2822 msg->cm_fields['M'] = NULL;
2826 /* Serialize our data structure for storage in the database */
2827 serialize_message(&smr, msg);
2830 msg->cm_fields['M'] = holdM;
2834 cprintf("%d Unable to serialize message\n",
2835 ERROR + INTERNAL_ERROR);
2839 /* Write our little bundle of joy into the message base */
2840 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2841 smr.ser, smr.len) < 0) {
2842 syslog(LOG_ERR, "Can't store message\n");
2846 cdb_store(CDB_BIGMSGS,
2856 /* Free the memory we used for the serialized message */
2859 /* Return the *local* message ID to the caller
2860 * (even if we're storing an incoming network message)
2868 * Serialize a struct CtdlMessage into the format used on disk and network.
2870 * This function loads up a "struct ser_ret" (defined in server.h) which
2871 * contains the length of the serialized message and a pointer to the
2872 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2874 void serialize_message(struct ser_ret *ret, /* return values */
2875 struct CtdlMessage *msg) /* unserialized msg */
2877 size_t wlen, fieldlen;
2879 static char *forder = FORDER;
2882 * Check for valid message format
2884 if (is_valid_message(msg) == 0) {
2885 syslog(LOG_ERR, "serialize_message() aborting due to invalid message\n");
2892 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2893 ret->len = ret->len +
2894 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2896 ret->ser = malloc(ret->len);
2897 if (ret->ser == NULL) {
2898 syslog(LOG_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2899 (long)ret->len, strerror(errno));
2906 ret->ser[1] = msg->cm_anon_type;
2907 ret->ser[2] = msg->cm_format_type;
2910 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2911 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2912 ret->ser[wlen++] = (char)forder[i];
2913 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2914 wlen = wlen + fieldlen + 1;
2916 if (ret->len != wlen) syslog(LOG_ERR, "ERROR: len=%ld wlen=%ld\n",
2917 (long)ret->len, (long)wlen);
2924 * Serialize a struct CtdlMessage into the format used on disk and network.
2926 * This function loads up a "struct ser_ret" (defined in server.h) which
2927 * contains the length of the serialized message and a pointer to the
2928 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2930 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2931 long Siz) /* how many chars ? */
2935 static char *forder = FORDER;
2939 * Check for valid message format
2941 if (is_valid_message(msg) == 0) {
2942 syslog(LOG_ERR, "dump_message() aborting due to invalid message\n");
2946 buf = (char*) malloc (Siz + 1);
2950 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2951 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2952 msg->cm_fields[(int)forder[i]]);
2953 if (client_write (buf, strlen(buf)) == -1)
2955 syslog(LOG_ERR, "dump_message(): aborting due to write failure.\n");
2966 * Check to see if any messages already exist in the current room which
2967 * carry the same Exclusive ID as this one. If any are found, delete them.
2969 void ReplicationChecks(struct CtdlMessage *msg) {
2970 long old_msgnum = (-1L);
2972 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2974 syslog(LOG_DEBUG, "Performing replication checks in <%s>\n",
2977 /* No exclusive id? Don't do anything. */
2978 if (msg == NULL) return;
2979 if (msg->cm_fields['E'] == NULL) return;
2980 if (IsEmptyStr(msg->cm_fields['E'])) return;
2981 /*syslog(LOG_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2982 msg->cm_fields['E'], CC->room.QRname);*/
2984 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2985 if (old_msgnum > 0L) {
2986 syslog(LOG_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2987 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2994 * Save a message to disk and submit it into the delivery system.
2996 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2997 struct recptypes *recps, /* recipients (if mail) */
2998 const char *force, /* force a particular room? */
2999 int flags /* should the message be exported clean? */
3001 char submit_filename[128];
3002 char generated_timestamp[32];
3003 char hold_rm[ROOMNAMELEN];
3004 char actual_rm[ROOMNAMELEN];
3005 char force_room[ROOMNAMELEN];
3006 char content_type[SIZ]; /* We have to learn this */
3007 char recipient[SIZ];
3009 const char *mptr = NULL;
3010 struct ctdluser userbuf;
3012 struct MetaData smi;
3013 FILE *network_fp = NULL;
3014 static int seqnum = 1;
3015 struct CtdlMessage *imsg = NULL;
3017 size_t instr_alloc = 0;
3019 char *hold_R, *hold_D;
3020 char *collected_addresses = NULL;
3021 struct addresses_to_be_filed *aptr = NULL;
3022 StrBuf *saved_rfc822_version = NULL;
3023 int qualified_for_journaling = 0;
3024 CitContext *CCC = MyContext();
3025 char bounce_to[1024] = "";
3029 syslog(LOG_DEBUG, "CtdlSubmitMsg() called\n");
3030 if (is_valid_message(msg) == 0) return(-1); /* self check */
3032 /* If this message has no timestamp, we take the liberty of
3033 * giving it one, right now.
3035 if (msg->cm_fields['T'] == NULL) {
3036 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
3037 msg->cm_fields['T'] = strdup(generated_timestamp);
3040 /* If this message has no path, we generate one.
3042 if (msg->cm_fields['P'] == NULL) {
3043 if (msg->cm_fields['A'] != NULL) {
3044 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
3045 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
3046 if (isspace(msg->cm_fields['P'][a])) {
3047 msg->cm_fields['P'][a] = ' ';
3052 msg->cm_fields['P'] = strdup("unknown");
3056 if (force == NULL) {
3057 strcpy(force_room, "");
3060 strcpy(force_room, force);
3063 /* Learn about what's inside, because it's what's inside that counts */
3064 if (msg->cm_fields['M'] == NULL) {
3065 syslog(LOG_ERR, "ERROR: attempt to save message with NULL body\n");
3069 switch (msg->cm_format_type) {
3071 strcpy(content_type, "text/x-citadel-variformat");
3074 strcpy(content_type, "text/plain");
3077 strcpy(content_type, "text/plain");
3078 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
3081 safestrncpy(content_type, &mptr[13], sizeof content_type);
3082 striplt(content_type);
3083 aptr = content_type;
3084 while (!IsEmptyStr(aptr)) {
3096 /* Goto the correct room */
3097 syslog(LOG_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
3098 strcpy(hold_rm, CCC->room.QRname);
3099 strcpy(actual_rm, CCC->room.QRname);
3100 if (recps != NULL) {
3101 strcpy(actual_rm, SENTITEMS);
3104 /* If the user is a twit, move to the twit room for posting */
3106 if (CCC->user.axlevel == AxProbU) {
3107 strcpy(hold_rm, actual_rm);
3108 strcpy(actual_rm, config.c_twitroom);
3109 syslog(LOG_DEBUG, "Diverting to twit room\n");
3113 /* ...or if this message is destined for Aide> then go there. */
3114 if (!IsEmptyStr(force_room)) {
3115 strcpy(actual_rm, force_room);
3118 syslog(LOG_DEBUG, "Final selection: %s\n", actual_rm);
3119 if (strcasecmp(actual_rm, CCC->room.QRname)) {
3120 /* CtdlGetRoom(&CCC->room, actual_rm); */
3121 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
3125 * If this message has no O (room) field, generate one.
3127 if (msg->cm_fields['O'] == NULL) {
3128 msg->cm_fields['O'] = strdup(CCC->room.QRname);
3131 /* Perform "before save" hooks (aborting if any return nonzero) */
3132 syslog(LOG_DEBUG, "Performing before-save hooks\n");
3133 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
3136 * If this message has an Exclusive ID, and the room is replication
3137 * checking enabled, then do replication checks.
3139 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
3140 ReplicationChecks(msg);
3143 /* Save it to disk */
3144 syslog(LOG_DEBUG, "Saving to disk\n");
3145 newmsgid = send_message(msg);
3146 if (newmsgid <= 0L) return(-5);
3148 /* Write a supplemental message info record. This doesn't have to
3149 * be a critical section because nobody else knows about this message
3152 syslog(LOG_DEBUG, "Creating MetaData record\n");
3153 memset(&smi, 0, sizeof(struct MetaData));
3154 smi.meta_msgnum = newmsgid;
3155 smi.meta_refcount = 0;
3156 safestrncpy(smi.meta_content_type, content_type,
3157 sizeof smi.meta_content_type);
3160 * Measure how big this message will be when rendered as RFC822.
3161 * We do this for two reasons:
3162 * 1. We need the RFC822 length for the new metadata record, so the
3163 * POP and IMAP services don't have to calculate message lengths
3164 * while the user is waiting (multiplied by potentially hundreds
3165 * or thousands of messages).
3166 * 2. If journaling is enabled, we will need an RFC822 version of the
3167 * message to attach to the journalized copy.
3169 if (CCC->redirect_buffer != NULL) {
3170 syslog(LOG_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3173 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3174 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3175 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3176 saved_rfc822_version = CCC->redirect_buffer;
3177 CCC->redirect_buffer = NULL;
3181 /* Now figure out where to store the pointers */
3182 syslog(LOG_DEBUG, "Storing pointers\n");
3184 /* If this is being done by the networker delivering a private
3185 * message, we want to BYPASS saving the sender's copy (because there
3186 * is no local sender; it would otherwise go to the Trashcan).
3188 if ((!CCC->internal_pgm) || (recps == NULL)) {
3189 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3190 syslog(LOG_ERR, "ERROR saving message pointer!\n");
3191 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3195 /* For internet mail, drop a copy in the outbound queue room */
3196 if ((recps != NULL) && (recps->num_internet > 0)) {
3197 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3200 /* If other rooms are specified, drop them there too. */
3201 if ((recps != NULL) && (recps->num_room > 0))
3202 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3203 extract_token(recipient, recps->recp_room, i,
3204 '|', sizeof recipient);
3205 syslog(LOG_DEBUG, "Delivering to room <%s>\n", recipient);
3206 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3209 /* Bump this user's messages posted counter. */
3210 syslog(LOG_DEBUG, "Updating user\n");
3211 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3212 CCC->user.posted = CCC->user.posted + 1;
3213 CtdlPutUserLock(&CCC->user);
3215 /* Decide where bounces need to be delivered */
3216 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3217 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3219 else if (CCC->logged_in) {
3220 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3223 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3226 /* If this is private, local mail, make a copy in the
3227 * recipient's mailbox and bump the reference count.
3229 if ((recps != NULL) && (recps->num_local > 0))
3230 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3231 extract_token(recipient, recps->recp_local, i,
3232 '|', sizeof recipient);
3233 syslog(LOG_DEBUG, "Delivering private local mail to <%s>\n",
3235 if (CtdlGetUser(&userbuf, recipient) == 0) {
3236 // Add a flag so the Funambol module knows its mail
3237 msg->cm_fields['W'] = strdup(recipient);
3238 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3239 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3240 CtdlBumpNewMailCounter(userbuf.usernum);
3241 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3242 /* Generate a instruction message for the Funambol notification
3243 * server, in the same style as the SMTP queue
3246 instr = malloc(instr_alloc);
3247 snprintf(instr, instr_alloc,
3248 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3250 SPOOLMIME, newmsgid, (long)time(NULL),
3254 imsg = malloc(sizeof(struct CtdlMessage));
3255 memset(imsg, 0, sizeof(struct CtdlMessage));
3256 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3257 imsg->cm_anon_type = MES_NORMAL;
3258 imsg->cm_format_type = FMT_RFC822;
3259 imsg->cm_fields['A'] = strdup("Citadel");
3260 imsg->cm_fields['J'] = strdup("do not journal");
3261 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3262 imsg->cm_fields['W'] = strdup(recipient);
3263 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3264 CtdlFreeMessage(imsg);
3268 syslog(LOG_DEBUG, "No user <%s>\n", recipient);
3269 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3274 /* Perform "after save" hooks */
3275 syslog(LOG_DEBUG, "Performing after-save hooks\n");
3276 PerformMessageHooks(msg, EVT_AFTERSAVE);
3278 /* For IGnet mail, we have to save a new copy into the spooler for
3279 * each recipient, with the R and D fields set to the recipient and
3280 * destination-node. This has two ugly side effects: all other
3281 * recipients end up being unlisted in this recipient's copy of the
3282 * message, and it has to deliver multiple messages to the same
3283 * node. We'll revisit this again in a year or so when everyone has
3284 * a network spool receiver that can handle the new style messages.
3286 if ((recps != NULL) && (recps->num_ignet > 0))
3287 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3288 extract_token(recipient, recps->recp_ignet, i,
3289 '|', sizeof recipient);
3291 hold_R = msg->cm_fields['R'];
3292 hold_D = msg->cm_fields['D'];
3293 msg->cm_fields['R'] = malloc(SIZ);
3294 msg->cm_fields['D'] = malloc(128);
3295 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3296 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3298 serialize_message(&smr, msg);
3300 snprintf(submit_filename, sizeof submit_filename,
3301 "%s/netmail.%04lx.%04x.%04x",
3303 (long) getpid(), CCC->cs_pid, ++seqnum);
3304 network_fp = fopen(submit_filename, "wb+");
3305 if (network_fp != NULL) {
3306 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3312 free(msg->cm_fields['R']);
3313 free(msg->cm_fields['D']);
3314 msg->cm_fields['R'] = hold_R;
3315 msg->cm_fields['D'] = hold_D;
3318 /* Go back to the room we started from */
3319 syslog(LOG_DEBUG, "Returning to original room %s\n", hold_rm);
3320 if (strcasecmp(hold_rm, CCC->room.QRname))
3321 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3323 /* For internet mail, generate delivery instructions.
3324 * Yes, this is recursive. Deal with it. Infinite recursion does
3325 * not happen because the delivery instructions message does not
3326 * contain a recipient.
3328 if ((recps != NULL) && (recps->num_internet > 0)) {
3329 syslog(LOG_DEBUG, "Generating delivery instructions\n");
3331 instr = malloc(instr_alloc);
3332 snprintf(instr, instr_alloc,
3333 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3335 SPOOLMIME, newmsgid, (long)time(NULL),
3339 if (recps->envelope_from != NULL) {
3340 tmp = strlen(instr);
3341 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3344 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3345 tmp = strlen(instr);
3346 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3347 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3348 instr_alloc = instr_alloc * 2;
3349 instr = realloc(instr, instr_alloc);
3351 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3354 imsg = malloc(sizeof(struct CtdlMessage));
3355 memset(imsg, 0, sizeof(struct CtdlMessage));
3356 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3357 imsg->cm_anon_type = MES_NORMAL;
3358 imsg->cm_format_type = FMT_RFC822;
3359 imsg->cm_fields['A'] = strdup("Citadel");
3360 imsg->cm_fields['J'] = strdup("do not journal");
3361 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3362 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3363 CtdlFreeMessage(imsg);
3367 * Any addresses to harvest for someone's address book?
3369 if ( (CCC->logged_in) && (recps != NULL) ) {
3370 collected_addresses = harvest_collected_addresses(msg);
3373 if (collected_addresses != NULL) {
3374 aptr = (struct addresses_to_be_filed *)
3375 malloc(sizeof(struct addresses_to_be_filed));
3376 CtdlMailboxName(actual_rm, sizeof actual_rm,
3377 &CCC->user, USERCONTACTSROOM);
3378 aptr->roomname = strdup(actual_rm);
3379 aptr->collected_addresses = collected_addresses;
3380 begin_critical_section(S_ATBF);
3383 end_critical_section(S_ATBF);
3387 * Determine whether this message qualifies for journaling.
3389 if (msg->cm_fields['J'] != NULL) {
3390 qualified_for_journaling = 0;
3393 if (recps == NULL) {
3394 qualified_for_journaling = config.c_journal_pubmsgs;
3396 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3397 qualified_for_journaling = config.c_journal_email;
3400 qualified_for_journaling = config.c_journal_pubmsgs;
3405 * Do we have to perform journaling? If so, hand off the saved
3406 * RFC822 version will be handed off to the journaler for background
3407 * submit. Otherwise, we have to free the memory ourselves.
3409 if (saved_rfc822_version != NULL) {
3410 if (qualified_for_journaling) {
3411 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3414 FreeStrBuf(&saved_rfc822_version);
3424 void aide_message (char *text, char *subject)
3426 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3431 * Convenience function for generating small administrative messages.
3433 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3434 int format_type, const char *subject)
3436 struct CtdlMessage *msg;
3437 struct recptypes *recp = NULL;
3439 msg = malloc(sizeof(struct CtdlMessage));
3440 memset(msg, 0, sizeof(struct CtdlMessage));
3441 msg->cm_magic = CTDLMESSAGE_MAGIC;
3442 msg->cm_anon_type = MES_NORMAL;
3443 msg->cm_format_type = format_type;
3446 msg->cm_fields['A'] = strdup(from);
3448 else if (fromaddr != NULL) {
3449 msg->cm_fields['A'] = strdup(fromaddr);
3450 if (strchr(msg->cm_fields['A'], '@')) {
3451 *strchr(msg->cm_fields['A'], '@') = 0;
3455 msg->cm_fields['A'] = strdup("Citadel");
3458 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3459 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3460 msg->cm_fields['N'] = strdup(NODENAME);
3462 msg->cm_fields['R'] = strdup(to);
3463 recp = validate_recipients(to, NULL, 0);
3465 if (subject != NULL) {
3466 msg->cm_fields['U'] = strdup(subject);
3468 msg->cm_fields['M'] = strdup(text);
3470 CtdlSubmitMsg(msg, recp, room, 0);
3471 CtdlFreeMessage(msg);
3472 if (recp != NULL) free_recipients(recp);
3478 * Back end function used by CtdlMakeMessage() and similar functions
3480 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3482 size_t maxlen, /* maximum message length */
3483 char *exist, /* if non-null, append to it;
3484 exist is ALWAYS freed */
3485 int crlf, /* CRLF newlines instead of LF */
3486 int *sock /* socket handle or 0 for this session's client socket */
3495 LineBuf = NewStrBufPlain(NULL, SIZ);
3496 if (exist == NULL) {
3497 Message = NewStrBufPlain(NULL, 4 * SIZ);
3500 Message = NewStrBufPlain(exist, -1);
3504 /* Do we need to change leading ".." to "." for SMTP escaping? */
3505 if ((tlen == 1) && (*terminator == '.')) {
3509 /* read in the lines of message text one by one */
3512 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3517 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3519 if ((StrLength(LineBuf) == tlen) &&
3520 (!strcmp(ChrPtr(LineBuf), terminator)))
3523 if ( (!flushing) && (!finished) ) {
3525 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3528 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3531 /* Unescape SMTP-style input of two dots at the beginning of the line */
3533 (StrLength(LineBuf) == 2) &&
3534 (!strcmp(ChrPtr(LineBuf), "..")))
3536 StrBufCutLeft(LineBuf, 1);
3539 StrBufAppendBuf(Message, LineBuf, 0);
3542 /* if we've hit the max msg length, flush the rest */
3543 if (StrLength(Message) >= maxlen) flushing = 1;
3545 } while (!finished);
3546 FreeStrBuf(&LineBuf);
3550 void DeleteAsyncMsg(ReadAsyncMsg **Msg)
3554 FreeStrBuf(&(*Msg)->MsgBuf);
3560 ReadAsyncMsg *NewAsyncMsg(const char *terminator, /* token signalling EOT */
3562 size_t maxlen, /* maximum message length */
3563 size_t expectlen, /* if we expect a message, how long should it be? */
3564 char *exist, /* if non-null, append to it;
3565 exist is ALWAYS freed */
3566 long eLen, /* length of exist */
3567 int crlf /* CRLF newlines instead of LF */
3570 ReadAsyncMsg *NewMsg;
3572 NewMsg = (ReadAsyncMsg *)malloc(sizeof(ReadAsyncMsg));
3573 memset(NewMsg, 0, sizeof(ReadAsyncMsg));
3575 if (exist == NULL) {
3578 if (expectlen == 0) {
3582 len = expectlen + 10;
3584 NewMsg->MsgBuf = NewStrBufPlain(NULL, len);
3587 NewMsg->MsgBuf = NewStrBufPlain(exist, eLen);
3590 /* Do we need to change leading ".." to "." for SMTP escaping? */
3591 if ((tlen == 1) && (*terminator == '.')) {
3595 NewMsg->terminator = terminator;
3596 NewMsg->tlen = tlen;
3598 NewMsg->maxlen = maxlen;
3600 NewMsg->crlf = crlf;
3606 * Back end function used by CtdlMakeMessage() and similar functions
3608 eReadState CtdlReadMessageBodyAsync(AsyncIO *IO)
3610 ReadAsyncMsg *ReadMsg;
3611 int MsgFinished = 0;
3612 eReadState Finished = eMustReadMore;
3617 const char *pch = ChrPtr(IO->SendBuf.Buf);
3618 const char *pchh = IO->SendBuf.ReadWritePointer;
3624 nbytes = StrLength(IO->SendBuf.Buf) - (pchh - pch);
3625 snprintf(fn, SIZ, "/tmp/foolog_ev_%s.%d",
3626 ((CitContext*)(IO->CitContext))->ServiceName,
3629 fd = fopen(fn, "a+");
3632 ReadMsg = IO->ReadMsg;
3634 /* read in the lines of message text one by one */
3636 Finished = StrBufChunkSipLine(IO->IOBuf, &IO->RecvBuf);
3639 case eMustReadMore: /// read new from socket...
3641 if (IO->RecvBuf.ReadWritePointer != NULL) {
3642 nbytes = StrLength(IO->RecvBuf.Buf) - (IO->RecvBuf.ReadWritePointer - ChrPtr(IO->RecvBuf.Buf));
3643 fprintf(fd, "Read; Line unfinished: %ld Bytes still in buffer [", nbytes);
3645 fwrite(IO->RecvBuf.ReadWritePointer, nbytes, 1, fd);
3649 fprintf(fd, "BufferEmpty! \n");
3655 case eBufferNotEmpty: /* shouldn't happen... */
3656 case eReadSuccess: /// done for now...
3658 case eReadFail: /// WHUT?
3664 if ((StrLength(IO->IOBuf) == ReadMsg->tlen) &&
3665 (!strcmp(ChrPtr(IO->IOBuf), ReadMsg->terminator))) {
3668 fprintf(fd, "found Terminator; Message Size: %d\n", StrLength(ReadMsg->MsgBuf));
3671 else if (!ReadMsg->flushing) {
3674 fprintf(fd, "Read Line: [%d][%s]\n", StrLength(IO->IOBuf), ChrPtr(IO->IOBuf));
3677 /* Unescape SMTP-style input of two dots at the beginning of the line */
3678 if ((ReadMsg->dodot) &&
3679 (StrLength(IO->IOBuf) == 2) && /* TODO: do we just unescape lines with two dots or any line? */
3680 (!strcmp(ChrPtr(IO->IOBuf), "..")))
3683 fprintf(fd, "UnEscaped!\n");
3685 StrBufCutLeft(IO->IOBuf, 1);
3688 if (ReadMsg->crlf) {
3689 StrBufAppendBufPlain(IO->IOBuf, HKEY("\r\n"), 0);
3692 StrBufAppendBufPlain(IO->IOBuf, HKEY("\n"), 0);
3695 StrBufAppendBuf(ReadMsg->MsgBuf, IO->IOBuf, 0);
3698 /* if we've hit the max msg length, flush the rest */
3699 if (StrLength(ReadMsg->MsgBuf) >= ReadMsg->maxlen) ReadMsg->flushing = 1;
3701 } while (!MsgFinished);
3704 fprintf(fd, "Done with reading; %s.\n, ",
3705 (MsgFinished)?"Message Finished": "FAILED");
3709 return eReadSuccess;
3716 * Back end function used by CtdlMakeMessage() and similar functions
3718 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3720 size_t maxlen, /* maximum message length */
3721 char *exist, /* if non-null, append to it;
3722 exist is ALWAYS freed */
3723 int crlf, /* CRLF newlines instead of LF */
3724 int *sock /* socket handle or 0 for this session's client socket */
3729 Message = CtdlReadMessageBodyBuf(terminator,
3735 if (Message == NULL)
3738 return SmashStrBuf(&Message);
3743 * Build a binary message to be saved on disk.
3744 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3745 * will become part of the message. This means you are no longer
3746 * responsible for managing that memory -- it will be freed along with
3747 * the rest of the fields when CtdlFreeMessage() is called.)
3750 struct CtdlMessage *CtdlMakeMessage(
3751 struct ctdluser *author, /* author's user structure */
3752 char *recipient, /* NULL if it's not mail */
3753 char *recp_cc, /* NULL if it's not mail */
3754 char *room, /* room where it's going */
3755 int type, /* see MES_ types in header file */
3756 int format_type, /* variformat, plain text, MIME... */
3757 char *fake_name, /* who we're masquerading as */
3758 char *my_email, /* which of my email addresses to use (empty is ok) */
3759 char *subject, /* Subject (optional) */
3760 char *supplied_euid, /* ...or NULL if this is irrelevant */
3761 char *preformatted_text, /* ...or NULL to read text from client */
3762 char *references /* Thread references */
3764 char dest_node[256];
3766 struct CtdlMessage *msg;
3768 StrBuf *FakeEncAuthor = NULL;
3770 msg = malloc(sizeof(struct CtdlMessage));
3771 memset(msg, 0, sizeof(struct CtdlMessage));
3772 msg->cm_magic = CTDLMESSAGE_MAGIC;
3773 msg->cm_anon_type = type;
3774 msg->cm_format_type = format_type;
3776 /* Don't confuse the poor folks if it's not routed mail. */
3777 strcpy(dest_node, "");
3779 if (recipient != NULL) striplt(recipient);
3780 if (recp_cc != NULL) striplt(recp_cc);
3782 /* Path or Return-Path */
3783 if (my_email == NULL) my_email = "";
3785 if (!IsEmptyStr(my_email)) {
3786 msg->cm_fields['P'] = strdup(my_email);
3789 snprintf(buf, sizeof buf, "%s", author->fullname);
3790 msg->cm_fields['P'] = strdup(buf);
3792 convert_spaces_to_underscores(msg->cm_fields['P']);
3794 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3795 msg->cm_fields['T'] = strdup(buf);
3797 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3798 FakeAuthor = NewStrBufPlain (fake_name, -1);
3801 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3803 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3804 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3805 FreeStrBuf(&FakeAuthor);
3807 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3808 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3811 msg->cm_fields['O'] = strdup(CC->room.QRname);
3814 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3815 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3817 if ((recipient != NULL) && (recipient[0] != 0)) {
3818 msg->cm_fields['R'] = strdup(recipient);
3820 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3821 msg->cm_fields['Y'] = strdup(recp_cc);
3823 if (dest_node[0] != 0) {
3824 msg->cm_fields['D'] = strdup(dest_node);
3827 if (!IsEmptyStr(my_email)) {
3828 msg->cm_fields['F'] = strdup(my_email);
3830 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3831 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3834 if (subject != NULL) {
3837 length = strlen(subject);
3843 while ((subject[i] != '\0') &&
3844 (IsAscii = isascii(subject[i]) != 0 ))
3847 msg->cm_fields['U'] = strdup(subject);
3848 else /* ok, we've got utf8 in the string. */
3850 msg->cm_fields['U'] = rfc2047encode(subject, length);
3856 if (supplied_euid != NULL) {
3857 msg->cm_fields['E'] = strdup(supplied_euid);
3860 if (references != NULL) {
3861 if (!IsEmptyStr(references)) {
3862 msg->cm_fields['W'] = strdup(references);
3866 if (preformatted_text != NULL) {
3867 msg->cm_fields['M'] = preformatted_text;
3870 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3878 * Check to see whether we have permission to post a message in the current
3879 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3880 * returns 0 on success.
3882 int CtdlDoIHavePermissionToPostInThisRoom(
3885 const char* RemoteIdentifier,
3891 if (!(CC->logged_in) &&
3892 (PostPublic == POST_LOGGED_IN)) {
3893 snprintf(errmsgbuf, n, "Not logged in.");
3894 return (ERROR + NOT_LOGGED_IN);
3896 else if (PostPublic == CHECK_EXISTANCE) {
3897 return (0); // We're Evaling whether a recipient exists
3899 else if (!(CC->logged_in)) {
3901 if ((CC->room.QRflags & QR_READONLY)) {
3902 snprintf(errmsgbuf, n, "Not logged in.");
3903 return (ERROR + NOT_LOGGED_IN);
3905 if (CC->room.QRflags2 & QR2_MODERATED) {
3906 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3907 return (ERROR + NOT_LOGGED_IN);
3909 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3914 if (RemoteIdentifier == NULL)
3916 snprintf(errmsgbuf, n, "Need sender to permit access.");
3917 return (ERROR + USERNAME_REQUIRED);
3920 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3921 begin_critical_section(S_NETCONFIGS);
3922 if (!read_spoolcontrol_file(&sc, filename))
3924 end_critical_section(S_NETCONFIGS);
3925 snprintf(errmsgbuf, n,
3926 "This mailing list only accepts posts from subscribers.");
3927 return (ERROR + NO_SUCH_USER);
3929 end_critical_section(S_NETCONFIGS);
3930 found = is_recipient (sc, RemoteIdentifier);
3931 free_spoolcontrol_struct(&sc);
3936 snprintf(errmsgbuf, n,
3937 "This mailing list only accepts posts from subscribers.");
3938 return (ERROR + NO_SUCH_USER);
3945 if ((CC->user.axlevel < AxProbU)
3946 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3947 snprintf(errmsgbuf, n, "Need to be validated to enter (except in %s> to sysop)", MAILROOM);
3948 return (ERROR + HIGHER_ACCESS_REQUIRED);
3951 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3953 if (ra & UA_POSTALLOWED) {
3954 strcpy(errmsgbuf, "OK to post or reply here");
3958 if ( (ra & UA_REPLYALLOWED) && (is_reply) ) {
3960 * To be thorough, we ought to check to see if the message they are
3961 * replying to is actually a valid one in this room, but unless this
3962 * actually becomes a problem we'll go with high performance instead.
3964 strcpy(errmsgbuf, "OK to reply here");
3968 if ( (ra & UA_REPLYALLOWED) && (!is_reply) ) {
3969 /* Clarify what happened with a better error message */
3970 snprintf(errmsgbuf, n, "You may only reply to existing messages here.");
3971 return (ERROR + HIGHER_ACCESS_REQUIRED);
3974 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3975 return (ERROR + HIGHER_ACCESS_REQUIRED);
3981 * Check to see if the specified user has Internet mail permission
3982 * (returns nonzero if permission is granted)
3984 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3986 /* Do not allow twits to send Internet mail */
3987 if (who->axlevel <= AxProbU) return(0);
3989 /* Globally enabled? */
3990 if (config.c_restrict == 0) return(1);
3992 /* User flagged ok? */
3993 if (who->flags & US_INTERNET) return(2);
3995 /* Aide level access? */
3996 if (who->axlevel >= AxAideU) return(3);
3998 /* No mail for you! */
4004 * Validate recipients, count delivery types and errors, and handle aliasing
4005 * FIXME check for dupes!!!!!
4007 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
4008 * were specified, or the number of addresses found invalid.
4010 * Caller needs to free the result using free_recipients()
4012 struct recptypes *validate_recipients(const char *supplied_recipients,
4013 const char *RemoteIdentifier,
4015 struct recptypes *ret;
4016 char *recipients = NULL;
4017 char this_recp[256];
4018 char this_recp_cooked[256];
4024 struct ctdluser tempUS;
4025 struct ctdlroom tempQR;
4026 struct ctdlroom tempQR2;
4032 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
4033 if (ret == NULL) return(NULL);
4035 /* Set all strings to null and numeric values to zero */
4036 memset(ret, 0, sizeof(struct recptypes));
4038 if (supplied_recipients == NULL) {
4039 recipients = strdup("");
4042 recipients = strdup(supplied_recipients);
4045 /* Allocate some memory. Yes, this allocates 500% more memory than we will
4046 * actually need, but it's healthier for the heap than doing lots of tiny
4047 * realloc() calls instead.
4050 ret->errormsg = malloc(strlen(recipients) + 1024);
4051 ret->recp_local = malloc(strlen(recipients) + 1024);
4052 ret->recp_internet = malloc(strlen(recipients) + 1024);
4053 ret->recp_ignet = malloc(strlen(recipients) + 1024);
4054 ret->recp_room = malloc(strlen(recipients) + 1024);
4055 ret->display_recp = malloc(strlen(recipients) + 1024);
4057 ret->errormsg[0] = 0;
4058 ret->recp_local[0] = 0;
4059 ret->recp_internet[0] = 0;
4060 ret->recp_ignet[0] = 0;
4061 ret->recp_room[0] = 0;
4062 ret->display_recp[0] = 0;
4064 ret->recptypes_magic = RECPTYPES_MAGIC;
4066 /* Change all valid separator characters to commas */
4067 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
4068 if ((recipients[i] == ';') || (recipients[i] == '|')) {
4069 recipients[i] = ',';
4073 /* Now start extracting recipients... */
4075 while (!IsEmptyStr(recipients)) {
4077 for (i=0; i<=strlen(recipients); ++i) {
4078 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
4079 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
4080 safestrncpy(this_recp, recipients, i+1);
4082 if (recipients[i] == ',') {
4083 strcpy(recipients, &recipients[i+1]);
4086 strcpy(recipients, "");
4093 if (IsEmptyStr(this_recp))
4095 syslog(LOG_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
4097 mailtype = alias(this_recp);
4098 mailtype = alias(this_recp);
4099 mailtype = alias(this_recp);
4101 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
4102 if (this_recp[j]=='_') {
4103 this_recp_cooked[j] = ' ';
4106 this_recp_cooked[j] = this_recp[j];
4109 this_recp_cooked[j] = '\0';
4114 if (!strcasecmp(this_recp, "sysop")) {
4116 strcpy(this_recp, config.c_aideroom);
4117 if (!IsEmptyStr(ret->recp_room)) {
4118 strcat(ret->recp_room, "|");
4120 strcat(ret->recp_room, this_recp);
4122 else if ( (!strncasecmp(this_recp, "room_", 5))
4123 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
4125 /* Save room so we can restore it later */
4129 /* Check permissions to send mail to this room */
4130 err = CtdlDoIHavePermissionToPostInThisRoom(
4135 0 /* 0 = not a reply */
4144 if (!IsEmptyStr(ret->recp_room)) {
4145 strcat(ret->recp_room, "|");
4147 strcat(ret->recp_room, &this_recp_cooked[5]);
4150 /* Restore room in case something needs it */
4154 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
4156 strcpy(this_recp, tempUS.fullname);
4157 if (!IsEmptyStr(ret->recp_local)) {
4158 strcat(ret->recp_local, "|");
4160 strcat(ret->recp_local, this_recp);
4162 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
4164 strcpy(this_recp, tempUS.fullname);
4165 if (!IsEmptyStr(ret->recp_local)) {
4166 strcat(ret->recp_local, "|");
4168 strcat(ret->recp_local, this_recp);
4176 /* Yes, you're reading this correctly: if the target
4177 * domain points back to the local system or an attached
4178 * Citadel directory, the address is invalid. That's
4179 * because if the address were valid, we would have
4180 * already translated it to a local address by now.
4182 if (IsDirectory(this_recp, 0)) {
4187 ++ret->num_internet;
4188 if (!IsEmptyStr(ret->recp_internet)) {
4189 strcat(ret->recp_internet, "|");
4191 strcat(ret->recp_internet, this_recp);
4196 if (!IsEmptyStr(ret->recp_ignet)) {
4197 strcat(ret->recp_ignet, "|");
4199 strcat(ret->recp_ignet, this_recp);
4207 if (IsEmptyStr(errmsg)) {
4208 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
4211 snprintf(append, sizeof append, "%s", errmsg);
4213 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
4214 if (!IsEmptyStr(ret->errormsg)) {
4215 strcat(ret->errormsg, "; ");
4217 strcat(ret->errormsg, append);
4221 if (IsEmptyStr(ret->display_recp)) {
4222 strcpy(append, this_recp);
4225 snprintf(append, sizeof append, ", %s", this_recp);
4227 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
4228 strcat(ret->display_recp, append);
4233 if ((ret->num_local + ret->num_internet + ret->num_ignet +
4234 ret->num_room + ret->num_error) == 0) {
4235 ret->num_error = (-1);
4236 strcpy(ret->errormsg, "No recipients specified.");
4239 syslog(LOG_DEBUG, "validate_recipients()\n");
4240 syslog(LOG_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
4241 syslog(LOG_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
4242 syslog(LOG_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
4243 syslog(LOG_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
4244 syslog(LOG_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
4252 * Destructor for struct recptypes
4254 void free_recipients(struct recptypes *valid) {
4256 if (valid == NULL) {
4260 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
4261 syslog(LOG_EMERG, "Attempt to call free_recipients() on some other data type!\n");
4265 if (valid->errormsg != NULL) free(valid->errormsg);
4266 if (valid->recp_local != NULL) free(valid->recp_local);
4267 if (valid->recp_internet != NULL) free(valid->recp_internet);
4268 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
4269 if (valid->recp_room != NULL) free(valid->recp_room);
4270 if (valid->display_recp != NULL) free(valid->display_recp);
4271 if (valid->bounce_to != NULL) free(valid->bounce_to);
4272 if (valid->envelope_from != NULL) free(valid->envelope_from);
4279 * message entry - mode 0 (normal)
4281 void cmd_ent0(char *entargs)
4287 char supplied_euid[128];
4289 int format_type = 0;
4290 char newusername[256];
4291 char newuseremail[256];
4292 struct CtdlMessage *msg;
4296 struct recptypes *valid = NULL;
4297 struct recptypes *valid_to = NULL;
4298 struct recptypes *valid_cc = NULL;
4299 struct recptypes *valid_bcc = NULL;
4301 int subject_required = 0;
4306 int newuseremail_ok = 0;
4307 char references[SIZ];
4312 post = extract_int(entargs, 0);
4313 extract_token(recp, entargs, 1, '|', sizeof recp);
4314 anon_flag = extract_int(entargs, 2);
4315 format_type = extract_int(entargs, 3);
4316 extract_token(subject, entargs, 4, '|', sizeof subject);
4317 extract_token(newusername, entargs, 5, '|', sizeof newusername);
4318 do_confirm = extract_int(entargs, 6);
4319 extract_token(cc, entargs, 7, '|', sizeof cc);
4320 extract_token(bcc, entargs, 8, '|', sizeof bcc);
4321 switch(CC->room.QRdefaultview) {
4324 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
4327 supplied_euid[0] = 0;
4330 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
4331 extract_token(references, entargs, 11, '|', sizeof references);
4332 for (ptr=references; *ptr != 0; ++ptr) {
4333 if (*ptr == '!') *ptr = '|';
4336 /* first check to make sure the request is valid. */
4338 err = CtdlDoIHavePermissionToPostInThisRoom(
4343 (!IsEmptyStr(references)) /* is this a reply? or a top-level post? */
4347 cprintf("%d %s\n", err, errmsg);
4351 /* Check some other permission type things. */
4353 if (IsEmptyStr(newusername)) {
4354 strcpy(newusername, CC->user.fullname);
4356 if ( (CC->user.axlevel < AxAideU)
4357 && (strcasecmp(newusername, CC->user.fullname))
4358 && (strcasecmp(newusername, CC->cs_inet_fn))
4360 cprintf("%d You don't have permission to author messages as '%s'.\n",
4361 ERROR + HIGHER_ACCESS_REQUIRED,
4368 if (IsEmptyStr(newuseremail)) {
4369 newuseremail_ok = 1;
4372 if (!IsEmptyStr(newuseremail)) {
4373 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4374 newuseremail_ok = 1;
4376 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4377 j = num_tokens(CC->cs_inet_other_emails, '|');
4378 for (i=0; i<j; ++i) {
4379 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4380 if (!strcasecmp(newuseremail, buf)) {
4381 newuseremail_ok = 1;
4387 if (!newuseremail_ok) {
4388 cprintf("%d You don't have permission to author messages as '%s'.\n",
4389 ERROR + HIGHER_ACCESS_REQUIRED,
4395 CC->cs_flags |= CS_POSTING;
4397 /* In mailbox rooms we have to behave a little differently --
4398 * make sure the user has specified at least one recipient. Then
4399 * validate the recipient(s). We do this for the Mail> room, as
4400 * well as any room which has the "Mailbox" view set - unless it
4401 * is the DRAFTS room which does not require recipients
4404 if ( ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4405 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4406 ) && (strcasecmp(&CC->room.QRname[11], USERDRAFTROOM)) !=0 ) {
4407 if (CC->user.axlevel < AxProbU) {
4408 strcpy(recp, "sysop");
4413 valid_to = validate_recipients(recp, NULL, 0);
4414 if (valid_to->num_error > 0) {
4415 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4416 free_recipients(valid_to);
4420 valid_cc = validate_recipients(cc, NULL, 0);
4421 if (valid_cc->num_error > 0) {
4422 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4423 free_recipients(valid_to);
4424 free_recipients(valid_cc);
4428 valid_bcc = validate_recipients(bcc, NULL, 0);
4429 if (valid_bcc->num_error > 0) {
4430 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4431 free_recipients(valid_to);
4432 free_recipients(valid_cc);
4433 free_recipients(valid_bcc);
4437 /* Recipient required, but none were specified */
4438 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4439 free_recipients(valid_to);
4440 free_recipients(valid_cc);
4441 free_recipients(valid_bcc);
4442 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4446 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4447 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4448 cprintf("%d You do not have permission "
4449 "to send Internet mail.\n",
4450 ERROR + HIGHER_ACCESS_REQUIRED);
4451 free_recipients(valid_to);
4452 free_recipients(valid_cc);
4453 free_recipients(valid_bcc);
4458 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)
4459 && (CC->user.axlevel < AxNetU) ) {
4460 cprintf("%d Higher access required for network mail.\n",
4461 ERROR + HIGHER_ACCESS_REQUIRED);
4462 free_recipients(valid_to);
4463 free_recipients(valid_cc);
4464 free_recipients(valid_bcc);
4468 if ((RESTRICT_INTERNET == 1)
4469 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4470 && ((CC->user.flags & US_INTERNET) == 0)
4471 && (!CC->internal_pgm)) {
4472 cprintf("%d You don't have access to Internet mail.\n",
4473 ERROR + HIGHER_ACCESS_REQUIRED);
4474 free_recipients(valid_to);
4475 free_recipients(valid_cc);
4476 free_recipients(valid_bcc);
4482 /* Is this a room which has anonymous-only or anonymous-option? */
4483 anonymous = MES_NORMAL;
4484 if (CC->room.QRflags & QR_ANONONLY) {
4485 anonymous = MES_ANONONLY;
4487 if (CC->room.QRflags & QR_ANONOPT) {
4488 if (anon_flag == 1) { /* only if the user requested it */
4489 anonymous = MES_ANONOPT;
4493 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4497 /* Recommend to the client that the use of a message subject is
4498 * strongly recommended in this room, if either the SUBJECTREQ flag
4499 * is set, or if there is one or more Internet email recipients.
4501 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4502 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4503 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4504 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4506 /* If we're only checking the validity of the request, return
4507 * success without creating the message.
4510 cprintf("%d %s|%d\n", CIT_OK,
4511 ((valid_to != NULL) ? valid_to->display_recp : ""),
4513 free_recipients(valid_to);
4514 free_recipients(valid_cc);
4515 free_recipients(valid_bcc);
4519 /* We don't need these anymore because we'll do it differently below */
4520 free_recipients(valid_to);
4521 free_recipients(valid_cc);
4522 free_recipients(valid_bcc);
4524 /* Read in the message from the client. */
4526 cprintf("%d send message\n", START_CHAT_MODE);
4528 cprintf("%d send message\n", SEND_LISTING);
4531 msg = CtdlMakeMessage(&CC->user, recp, cc,
4532 CC->room.QRname, anonymous, format_type,
4533 newusername, newuseremail, subject,
4534 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4537 /* Put together one big recipients struct containing to/cc/bcc all in
4538 * one. This is for the envelope.
4540 char *all_recps = malloc(SIZ * 3);
4541 strcpy(all_recps, recp);
4542 if (!IsEmptyStr(cc)) {
4543 if (!IsEmptyStr(all_recps)) {
4544 strcat(all_recps, ",");
4546 strcat(all_recps, cc);
4548 if (!IsEmptyStr(bcc)) {
4549 if (!IsEmptyStr(all_recps)) {
4550 strcat(all_recps, ",");
4552 strcat(all_recps, bcc);
4554 if (!IsEmptyStr(all_recps)) {
4555 valid = validate_recipients(all_recps, NULL, 0);
4563 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4566 cprintf("%ld\n", msgnum);
4568 cprintf("Message accepted.\n");
4571 cprintf("Internal error.\n");
4573 if (msg->cm_fields['E'] != NULL) {
4574 cprintf("%s\n", msg->cm_fields['E']);
4581 CtdlFreeMessage(msg);
4583 if (valid != NULL) {
4584 free_recipients(valid);
4592 * API function to delete messages which match a set of criteria
4593 * (returns the actual number of messages deleted)
4595 int CtdlDeleteMessages(char *room_name, /* which room */
4596 long *dmsgnums, /* array of msg numbers to be deleted */
4597 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4598 char *content_type /* or "" for any. regular expressions expected. */
4601 struct ctdlroom qrbuf;
4602 struct cdbdata *cdbfr;
4603 long *msglist = NULL;
4604 long *dellist = NULL;
4607 int num_deleted = 0;
4609 struct MetaData smi;
4612 int need_to_free_re = 0;
4614 if (content_type) if (!IsEmptyStr(content_type)) {
4615 regcomp(&re, content_type, 0);
4616 need_to_free_re = 1;
4618 syslog(LOG_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4619 room_name, num_dmsgnums, content_type);
4621 /* get room record, obtaining a lock... */
4622 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4623 syslog(LOG_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4625 if (need_to_free_re) regfree(&re);
4626 return (0); /* room not found */
4628 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4630 if (cdbfr != NULL) {
4631 dellist = malloc(cdbfr->len);
4632 msglist = (long *) cdbfr->ptr;
4633 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4634 num_msgs = cdbfr->len / sizeof(long);
4638 for (i = 0; i < num_msgs; ++i) {
4641 /* Set/clear a bit for each criterion */
4643 /* 0 messages in the list or a null list means that we are
4644 * interested in deleting any messages which meet the other criteria.
4646 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4647 delete_this |= 0x01;
4650 for (j=0; j<num_dmsgnums; ++j) {
4651 if (msglist[i] == dmsgnums[j]) {
4652 delete_this |= 0x01;
4657 if (IsEmptyStr(content_type)) {
4658 delete_this |= 0x02;
4660 GetMetaData(&smi, msglist[i]);
4661 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4662 delete_this |= 0x02;
4666 /* Delete message only if all bits are set */
4667 if (delete_this == 0x03) {
4668 dellist[num_deleted++] = msglist[i];
4673 num_msgs = sort_msglist(msglist, num_msgs);
4674 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4675 msglist, (int)(num_msgs * sizeof(long)));
4678 qrbuf.QRhighest = msglist[num_msgs - 1];
4680 qrbuf.QRhighest = 0;
4682 CtdlPutRoomLock(&qrbuf);
4684 /* Go through the messages we pulled out of the index, and decrement
4685 * their reference counts by 1. If this is the only room the message
4686 * was in, the reference count will reach zero and the message will
4687 * automatically be deleted from the database. We do this in a
4688 * separate pass because there might be plug-in hooks getting called,
4689 * and we don't want that happening during an S_ROOMS critical
4692 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4693 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4694 AdjRefCount(dellist[i], -1);
4697 /* Now free the memory we used, and go away. */
4698 if (msglist != NULL) free(msglist);
4699 if (dellist != NULL) free(dellist);
4700 syslog(LOG_DEBUG, "%d message(s) deleted.\n", num_deleted);
4701 if (need_to_free_re) regfree(&re);
4702 return (num_deleted);
4708 * Check whether the current user has permission to delete messages from
4709 * the current room (returns 1 for yes, 0 for no)
4711 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4713 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4714 if (ra & UA_DELETEALLOWED) return(1);
4722 * Delete message from current room
4724 void cmd_dele(char *args)
4733 extract_token(msgset, args, 0, '|', sizeof msgset);
4734 num_msgs = num_tokens(msgset, ',');
4736 cprintf("%d Nothing to do.\n", CIT_OK);
4740 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4741 cprintf("%d Higher access required.\n",
4742 ERROR + HIGHER_ACCESS_REQUIRED);
4747 * Build our message set to be moved/copied
4749 msgs = malloc(num_msgs * sizeof(long));
4750 for (i=0; i<num_msgs; ++i) {
4751 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4752 msgs[i] = atol(msgtok);
4755 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4759 cprintf("%d %d message%s deleted.\n", CIT_OK,
4760 num_deleted, ((num_deleted != 1) ? "s" : ""));
4762 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4770 * move or copy a message to another room
4772 void cmd_move(char *args)
4779 char targ[ROOMNAMELEN];
4780 struct ctdlroom qtemp;
4787 extract_token(msgset, args, 0, '|', sizeof msgset);
4788 num_msgs = num_tokens(msgset, ',');
4790 cprintf("%d Nothing to do.\n", CIT_OK);
4794 extract_token(targ, args, 1, '|', sizeof targ);
4795 convert_room_name_macros(targ, sizeof targ);
4796 targ[ROOMNAMELEN - 1] = 0;
4797 is_copy = extract_int(args, 2);
4799 if (CtdlGetRoom(&qtemp, targ) != 0) {
4800 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4804 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4805 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4809 CtdlGetUser(&CC->user, CC->curr_user);
4810 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4812 /* Check for permission to perform this operation.
4813 * Remember: "CC->room" is source, "qtemp" is target.
4817 /* Aides can move/copy */
4818 if (CC->user.axlevel >= AxAideU) permit = 1;
4820 /* Room aides can move/copy */
4821 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4823 /* Permit move/copy from personal rooms */
4824 if ((CC->room.QRflags & QR_MAILBOX)
4825 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4827 /* Permit only copy from public to personal room */
4829 && (!(CC->room.QRflags & QR_MAILBOX))
4830 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4832 /* Permit message removal from collaborative delete rooms */
4833 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4835 /* Users allowed to post into the target room may move into it too. */
4836 if ((CC->room.QRflags & QR_MAILBOX) &&
4837 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4839 /* User must have access to target room */
4840 if (!(ra & UA_KNOWN)) permit = 0;
4843 cprintf("%d Higher access required.\n",
4844 ERROR + HIGHER_ACCESS_REQUIRED);
4849 * Build our message set to be moved/copied
4851 msgs = malloc(num_msgs * sizeof(long));
4852 for (i=0; i<num_msgs; ++i) {
4853 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4854 msgs[i] = atol(msgtok);
4860 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4862 cprintf("%d Cannot store message(s) in %s: error %d\n",
4868 /* Now delete the message from the source room,
4869 * if this is a 'move' rather than a 'copy' operation.
4872 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4876 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4882 * GetMetaData() - Get the supplementary record for a message
4884 void GetMetaData(struct MetaData *smibuf, long msgnum)
4887 struct cdbdata *cdbsmi;
4890 memset(smibuf, 0, sizeof(struct MetaData));
4891 smibuf->meta_msgnum = msgnum;
4892 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4894 /* Use the negative of the message number for its supp record index */
4895 TheIndex = (0L - msgnum);
4897 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4898 if (cdbsmi == NULL) {
4899 return; /* record not found; go with defaults */
4901 memcpy(smibuf, cdbsmi->ptr,
4902 ((cdbsmi->len > sizeof(struct MetaData)) ?
4903 sizeof(struct MetaData) : cdbsmi->len));
4910 * PutMetaData() - (re)write supplementary record for a message
4912 void PutMetaData(struct MetaData *smibuf)
4916 /* Use the negative of the message number for the metadata db index */
4917 TheIndex = (0L - smibuf->meta_msgnum);
4919 cdb_store(CDB_MSGMAIN,
4920 &TheIndex, (int)sizeof(long),
4921 smibuf, (int)sizeof(struct MetaData));
4926 * AdjRefCount - submit an adjustment to the reference count for a message.
4927 * (These are just queued -- we actually process them later.)
4929 void AdjRefCount(long msgnum, int incr)
4931 struct arcq new_arcq;
4934 syslog(LOG_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4938 begin_critical_section(S_SUPPMSGMAIN);
4939 if (arcfp == NULL) {
4940 arcfp = fopen(file_arcq, "ab+");
4942 end_critical_section(S_SUPPMSGMAIN);
4944 /* msgnum < 0 means that we're trying to close the file */
4946 syslog(LOG_DEBUG, "Closing the AdjRefCount queue file\n");
4947 begin_critical_section(S_SUPPMSGMAIN);
4948 if (arcfp != NULL) {
4952 end_critical_section(S_SUPPMSGMAIN);
4957 * If we can't open the queue, perform the operation synchronously.
4959 if (arcfp == NULL) {
4960 TDAP_AdjRefCount(msgnum, incr);
4964 new_arcq.arcq_msgnum = msgnum;
4965 new_arcq.arcq_delta = incr;
4966 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4974 * TDAP_ProcessAdjRefCountQueue()
4976 * Process the queue of message count adjustments that was created by calls
4977 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4978 * for each one. This should be an "off hours" operation.
4980 int TDAP_ProcessAdjRefCountQueue(void)
4982 char file_arcq_temp[PATH_MAX];
4985 struct arcq arcq_rec;
4986 int num_records_processed = 0;
4988 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4990 begin_critical_section(S_SUPPMSGMAIN);
4991 if (arcfp != NULL) {
4996 r = link(file_arcq, file_arcq_temp);
4998 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4999 end_critical_section(S_SUPPMSGMAIN);
5000 return(num_records_processed);
5004 end_critical_section(S_SUPPMSGMAIN);
5006 fp = fopen(file_arcq_temp, "rb");
5008 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5009 return(num_records_processed);
5012 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
5013 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
5014 ++num_records_processed;
5018 r = unlink(file_arcq_temp);
5020 syslog(LOG_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
5023 return(num_records_processed);
5029 * TDAP_AdjRefCount - adjust the reference count for a message.
5030 * This one does it "for real" because it's called by
5031 * the autopurger function that processes the queue
5032 * created by AdjRefCount(). If a message's reference
5033 * count becomes zero, we also delete the message from
5034 * disk and de-index it.
5036 void TDAP_AdjRefCount(long msgnum, int incr)
5039 struct MetaData smi;
5042 /* This is a *tight* critical section; please keep it that way, as
5043 * it may get called while nested in other critical sections.
5044 * Complicating this any further will surely cause deadlock!
5046 begin_critical_section(S_SUPPMSGMAIN);
5047 GetMetaData(&smi, msgnum);
5048 smi.meta_refcount += incr;
5050 end_critical_section(S_SUPPMSGMAIN);
5051 syslog(LOG_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
5052 msgnum, incr, smi.meta_refcount
5055 /* If the reference count is now zero, delete the message
5056 * (and its supplementary record as well).
5058 if (smi.meta_refcount == 0) {
5059 syslog(LOG_DEBUG, "Deleting message <%ld>\n", msgnum);
5061 /* Call delete hooks with NULL room to show it has gone altogether */
5062 PerformDeleteHooks(NULL, msgnum);
5064 /* Remove from message base */
5066 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5067 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
5069 /* Remove metadata record */
5070 delnum = (0L - msgnum);
5071 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
5077 * Write a generic object to this room
5079 * Note: this could be much more efficient. Right now we use two temporary
5080 * files, and still pull the message into memory as with all others.
5082 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
5083 char *content_type, /* MIME type of this object */
5084 char *raw_message, /* Data to be written */
5085 off_t raw_length, /* Size of raw_message */
5086 struct ctdluser *is_mailbox, /* Mailbox room? */
5087 int is_binary, /* Is encoding necessary? */
5088 int is_unique, /* Del others of this type? */
5089 unsigned int flags /* Internal save flags */
5093 struct ctdlroom qrbuf;
5094 char roomname[ROOMNAMELEN];
5095 struct CtdlMessage *msg;
5096 char *encoded_message = NULL;
5098 if (is_mailbox != NULL) {
5099 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
5102 safestrncpy(roomname, req_room, sizeof(roomname));
5105 syslog(LOG_DEBUG, "Raw length is %ld\n", (long)raw_length);
5108 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
5111 encoded_message = malloc((size_t)(raw_length + 4096));
5114 sprintf(encoded_message, "Content-type: %s\n", content_type);
5117 sprintf(&encoded_message[strlen(encoded_message)],
5118 "Content-transfer-encoding: base64\n\n"
5122 sprintf(&encoded_message[strlen(encoded_message)],
5123 "Content-transfer-encoding: 7bit\n\n"
5129 &encoded_message[strlen(encoded_message)],
5137 &encoded_message[strlen(encoded_message)],
5143 syslog(LOG_DEBUG, "Allocating\n");
5144 msg = malloc(sizeof(struct CtdlMessage));
5145 memset(msg, 0, sizeof(struct CtdlMessage));
5146 msg->cm_magic = CTDLMESSAGE_MAGIC;
5147 msg->cm_anon_type = MES_NORMAL;
5148 msg->cm_format_type = 4;
5149 msg->cm_fields['A'] = strdup(CC->user.fullname);
5150 msg->cm_fields['O'] = strdup(req_room);
5151 msg->cm_fields['N'] = strdup(config.c_nodename);
5152 msg->cm_fields['H'] = strdup(config.c_humannode);
5153 msg->cm_flags = flags;
5155 msg->cm_fields['M'] = encoded_message;
5157 /* Create the requested room if we have to. */
5158 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
5159 CtdlCreateRoom(roomname,
5160 ( (is_mailbox != NULL) ? 5 : 3 ),
5161 "", 0, 1, 0, VIEW_BBS);
5163 /* If the caller specified this object as unique, delete all
5164 * other objects of this type that are currently in the room.
5167 syslog(LOG_DEBUG, "Deleted %d other msgs of this type\n",
5168 CtdlDeleteMessages(roomname, NULL, 0, content_type)
5171 /* Now write the data */
5172 CtdlSubmitMsg(msg, NULL, roomname, 0);
5173 CtdlFreeMessage(msg);
5181 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
5182 config_msgnum = msgnum;
5186 char *CtdlGetSysConfig(char *sysconfname) {
5187 char hold_rm[ROOMNAMELEN];
5190 struct CtdlMessage *msg;
5193 strcpy(hold_rm, CC->room.QRname);
5194 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
5195 CtdlGetRoom(&CC->room, hold_rm);
5200 /* We want the last (and probably only) config in this room */
5201 begin_critical_section(S_CONFIG);
5202 config_msgnum = (-1L);
5203 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
5204 CtdlGetSysConfigBackend, NULL);
5205 msgnum = config_msgnum;
5206 end_critical_section(S_CONFIG);
5212 msg = CtdlFetchMessage(msgnum, 1);
5214 conf = strdup(msg->cm_fields['M']);
5215 CtdlFreeMessage(msg);
5222 CtdlGetRoom(&CC->room, hold_rm);
5224 if (conf != NULL) do {
5225 extract_token(buf, conf, 0, '\n', sizeof buf);
5226 strcpy(conf, &conf[strlen(buf)+1]);
5227 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
5233 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
5234 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
5239 * Determine whether a given Internet address belongs to the current user
5241 int CtdlIsMe(char *addr, int addr_buf_len)
5243 struct recptypes *recp;
5246 recp = validate_recipients(addr, NULL, 0);
5247 if (recp == NULL) return(0);
5249 if (recp->num_local == 0) {
5250 free_recipients(recp);
5254 for (i=0; i<recp->num_local; ++i) {
5255 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
5256 if (!strcasecmp(addr, CC->user.fullname)) {
5257 free_recipients(recp);
5262 free_recipients(recp);
5268 * Citadel protocol command to do the same
5270 void cmd_isme(char *argbuf) {
5273 if (CtdlAccessCheck(ac_logged_in)) return;
5274 extract_token(addr, argbuf, 0, '|', sizeof addr);
5276 if (CtdlIsMe(addr, sizeof addr)) {
5277 cprintf("%d %s\n", CIT_OK, addr);
5280 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
5286 /*****************************************************************************/
5287 /* MODULE INITIALIZATION STUFF */
5288 /*****************************************************************************/
5290 CTDL_MODULE_INIT(msgbase)
5293 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
5294 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
5295 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
5296 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
5297 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
5298 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
5299 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
5300 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
5301 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
5302 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
5303 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
5304 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
5307 /* return our Subversion id for the Log */