4 * Implements the message store.
6 * Copyright (c) 1987-2010 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #if TIME_WITH_SYS_TIME
30 # include <sys/time.h>
34 # include <sys/time.h>
47 #include <sys/types.h>
49 #include <libcitadel.h>
52 #include "serv_extensions.h"
56 #include "sysdep_decls.h"
57 #include "citserver.h"
64 #include "internet_addressing.h"
65 #include "euidindex.h"
66 #include "journaling.h"
67 #include "citadel_dirs.h"
68 #include "clientsocket.h"
69 #include "serv_network.h"
72 #include "ctdl_module.h"
75 struct addresses_to_be_filed *atbf = NULL;
77 /* This temp file holds the queue of operations for AdjRefCount() */
78 static FILE *arcfp = NULL;
81 * This really belongs in serv_network.c, but I don't know how to export
82 * symbols between modules.
84 struct FilterList *filterlist = NULL;
88 * These are the four-character field headers we use when outputting
89 * messages in Citadel format (as opposed to RFC822 format).
92 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
93 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
94 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
95 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
96 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
97 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
98 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
99 NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
128 * This function is self explanatory.
129 * (What can I say, I'm in a weird mood today...)
131 void remove_any_whitespace_to_the_left_or_right_of_at_symbol(char *name)
135 for (i = 0; i < strlen(name); ++i) {
136 if (name[i] == '@') {
137 while (isspace(name[i - 1]) && i > 0) {
138 strcpy(&name[i - 1], &name[i]);
141 while (isspace(name[i + 1])) {
142 strcpy(&name[i + 1], &name[i + 2]);
150 * Aliasing for network mail.
151 * (Error messages have been commented out, because this is a server.)
153 int alias(char *name)
154 { /* process alias and routing info for mail */
157 char aaa[SIZ], bbb[SIZ];
158 char *ignetcfg = NULL;
159 char *ignetmap = NULL;
165 char original_name[256];
166 safestrncpy(original_name, name, sizeof original_name);
169 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
170 stripallbut(name, '<', '>');
172 fp = fopen(file_mail_aliases, "r");
174 fp = fopen("/dev/null", "r");
181 while (fgets(aaa, sizeof aaa, fp) != NULL) {
182 while (isspace(name[0]))
183 strcpy(name, &name[1]);
184 aaa[strlen(aaa) - 1] = 0;
186 for (a = 0; a < strlen(aaa); ++a) {
188 strcpy(bbb, &aaa[a + 1]);
192 if (!strcasecmp(name, aaa))
197 /* Hit the Global Address Book */
198 if (CtdlDirectoryLookup(aaa, name, sizeof aaa) == 0) {
202 if (strcasecmp(original_name, name)) {
203 CtdlLogPrintf(CTDL_INFO, "%s is being forwarded to %s\n", original_name, name);
206 /* Change "user @ xxx" to "user" if xxx is an alias for this host */
207 for (a=0; a<strlen(name); ++a) {
208 if (name[a] == '@') {
209 if (CtdlHostAlias(&name[a+1]) == hostalias_localhost) {
211 CtdlLogPrintf(CTDL_INFO, "Changed to <%s>\n", name);
216 /* determine local or remote type, see citadel.h */
217 at = haschar(name, '@');
218 if (at == 0) return(MES_LOCAL); /* no @'s - local address */
219 if (at > 1) return(MES_ERROR); /* >1 @'s - invalid address */
220 remove_any_whitespace_to_the_left_or_right_of_at_symbol(name);
222 /* figure out the delivery mode */
223 extract_token(node, name, 1, '@', sizeof node);
225 /* If there are one or more dots in the nodename, we assume that it
226 * is an FQDN and will attempt SMTP delivery to the Internet.
228 if (haschar(node, '.') > 0) {
229 return(MES_INTERNET);
232 /* Otherwise we look in the IGnet maps for a valid Citadel node.
233 * Try directly-connected nodes first...
235 ignetcfg = CtdlGetSysConfig(IGNETCFG);
236 for (i=0; i<num_tokens(ignetcfg, '\n'); ++i) {
237 extract_token(buf, ignetcfg, i, '\n', sizeof buf);
238 extract_token(testnode, buf, 0, '|', sizeof testnode);
239 if (!strcasecmp(node, testnode)) {
247 * Then try nodes that are two or more hops away.
249 ignetmap = CtdlGetSysConfig(IGNETMAP);
250 for (i=0; i<num_tokens(ignetmap, '\n'); ++i) {
251 extract_token(buf, ignetmap, i, '\n', sizeof buf);
252 extract_token(testnode, buf, 0, '|', sizeof testnode);
253 if (!strcasecmp(node, testnode)) {
260 /* If we get to this point it's an invalid node name */
266 * Back end for the MSGS command: output message number only.
268 void simple_listing(long msgnum, void *userdata)
270 cprintf("%ld\n", msgnum);
276 * Back end for the MSGS command: output header summary.
278 void headers_listing(long msgnum, void *userdata)
280 struct CtdlMessage *msg;
282 msg = CtdlFetchMessage(msgnum, 0);
284 cprintf("%ld|0|||||\n", msgnum);
288 cprintf("%ld|%s|%s|%s|%s|%s|\n",
290 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"),
291 (msg->cm_fields['A'] ? msg->cm_fields['A'] : ""),
292 (msg->cm_fields['N'] ? msg->cm_fields['N'] : ""),
293 (msg->cm_fields['F'] ? msg->cm_fields['F'] : ""),
294 (msg->cm_fields['U'] ? msg->cm_fields['U'] : "")
296 CtdlFreeMessage(msg);
300 * Back end for the MSGS command: output EUID header.
302 void headers_euid(long msgnum, void *userdata)
304 struct CtdlMessage *msg;
306 msg = CtdlFetchMessage(msgnum, 0);
308 cprintf("%ld||\n", msgnum);
312 cprintf("%ld|%s|%s\n",
314 (msg->cm_fields['E'] ? msg->cm_fields['E'] : ""),
315 (msg->cm_fields['T'] ? msg->cm_fields['T'] : "0"));
316 CtdlFreeMessage(msg);
323 /* Determine if a given message matches the fields in a message template.
324 * Return 0 for a successful match.
326 int CtdlMsgCmp(struct CtdlMessage *msg, struct CtdlMessage *template) {
329 /* If there aren't any fields in the template, all messages will
332 if (template == NULL) return(0);
334 /* Null messages are bogus. */
335 if (msg == NULL) return(1);
337 for (i='A'; i<='Z'; ++i) {
338 if (template->cm_fields[i] != NULL) {
339 if (msg->cm_fields[i] == NULL) {
340 /* Considered equal if temmplate is empty string */
341 if (IsEmptyStr(template->cm_fields[i])) continue;
344 if (strcasecmp(msg->cm_fields[i],
345 template->cm_fields[i])) return 1;
349 /* All compares succeeded: we have a match! */
356 * Retrieve the "seen" message list for the current room.
358 void CtdlGetSeen(char *buf, int which_set) {
361 /* Learn about the user and room in question */
362 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
364 if (which_set == ctdlsetseen_seen)
365 safestrncpy(buf, vbuf.v_seen, SIZ);
366 if (which_set == ctdlsetseen_answered)
367 safestrncpy(buf, vbuf.v_answered, SIZ);
373 * Manipulate the "seen msgs" string (or other message set strings)
375 void CtdlSetSeen(long *target_msgnums, int num_target_msgnums,
376 int target_setting, int which_set,
377 struct ctdluser *which_user, struct ctdlroom *which_room) {
378 struct cdbdata *cdbfr;
392 char *is_set; /* actually an array of booleans */
394 /* Don't bother doing *anything* if we were passed a list of zero messages */
395 if (num_target_msgnums < 1) {
399 /* If no room was specified, we go with the current room. */
401 which_room = &CC->room;
404 /* If no user was specified, we go with the current user. */
406 which_user = &CC->user;
409 CtdlLogPrintf(CTDL_DEBUG, "CtdlSetSeen(%d msgs starting with %ld, %s, %d) in <%s>\n",
410 num_target_msgnums, target_msgnums[0],
411 (target_setting ? "SET" : "CLEAR"),
415 /* Learn about the user and room in question */
416 CtdlGetRelationship(&vbuf, which_user, which_room);
418 /* Load the message list */
419 cdbfr = cdb_fetch(CDB_MSGLISTS, &which_room->QRnumber, sizeof(long));
421 msglist = (long *) cdbfr->ptr;
422 cdbfr->ptr = NULL; /* CtdlSetSeen() now owns this memory */
423 num_msgs = cdbfr->len / sizeof(long);
426 return; /* No messages at all? No further action. */
429 is_set = malloc(num_msgs * sizeof(char));
430 memset(is_set, 0, (num_msgs * sizeof(char)) );
432 /* Decide which message set we're manipulating */
434 case ctdlsetseen_seen:
435 vset = NewStrBufPlain(vbuf.v_seen, -1);
437 case ctdlsetseen_answered:
438 vset = NewStrBufPlain(vbuf.v_answered, -1);
445 #if 0 /* This is a special diagnostic section. Do not allow it to run during normal operation. */
446 CtdlLogPrintf(CTDL_DEBUG, "There are %d messages in the room.\n", num_msgs);
447 for (i=0; i<num_msgs; ++i) {
448 if ((i > 0) && (msglist[i] <= msglist[i-1])) abort();
450 CtdlLogPrintf(CTDL_DEBUG, "We are twiddling %d of them.\n", num_target_msgnums);
451 for (k=0; k<num_target_msgnums; ++k) {
452 if ((k > 0) && (target_msgnums[k] <= target_msgnums[k-1])) abort();
456 CtdlLogPrintf(CTDL_DEBUG, "before update: %s\n", ChrPtr(vset));
458 /* Translate the existing sequence set into an array of booleans */
459 setstr = NewStrBuf();
463 while (StrBufExtract_NextToken(setstr, vset, &pvset, ',') >= 0) {
465 StrBufExtract_token(lostr, setstr, 0, ':');
466 if (StrBufNum_tokens(setstr, ':') >= 2) {
467 StrBufExtract_token(histr, setstr, 1, ':');
471 StrBufAppendBuf(histr, lostr, 0);
474 if (!strcmp(ChrPtr(histr), "*")) {
481 for (i = 0; i < num_msgs; ++i) {
482 if ((msglist[i] >= lo) && (msglist[i] <= hi)) {
492 /* Now translate the array of booleans back into a sequence set */
498 for (i=0; i<num_msgs; ++i) {
502 for (k=0; k<num_target_msgnums; ++k) {
503 if (msglist[i] == target_msgnums[k]) {
504 is_seen = target_setting;
508 if ((was_seen == 0) && (is_seen == 1)) {
511 else if ((was_seen == 1) && (is_seen == 0)) {
514 if (StrLength(vset) > 0) {
515 StrBufAppendBufPlain(vset, HKEY(","), 0);
518 StrBufAppendPrintf(vset, "%ld", hi);
521 StrBufAppendPrintf(vset, "%ld:%ld", lo, hi);
525 if ((is_seen) && (i == num_msgs - 1)) {
526 if (StrLength(vset) > 0) {
527 StrBufAppendBufPlain(vset, HKEY(","), 0);
529 if ((i==0) || (was_seen == 0)) {
530 StrBufAppendPrintf(vset, "%ld", msglist[i]);
533 StrBufAppendPrintf(vset, "%ld:%ld", lo, msglist[i]);
541 * We will have to stuff this string back into a 4096 byte buffer, so if it's
542 * larger than that now, truncate it by removing tokens from the beginning.
543 * The limit of 100 iterations is there to prevent an infinite loop in case
544 * something unexpected happens.
546 int number_of_truncations = 0;
547 while ( (StrLength(vset) > SIZ) && (number_of_truncations < 100) ) {
548 StrBufRemove_token(vset, 0, ',');
549 ++number_of_truncations;
553 * If we're truncating the sequence set of messages marked with the 'seen' flag,
554 * we want the earliest messages (the truncated ones) to be marked, not unmarked.
555 * Otherwise messages at the beginning will suddenly appear to be 'unseen'.
557 if ( (which_set == ctdlsetseen_seen) && (number_of_truncations > 0) ) {
559 first_tok = NewStrBuf();
560 StrBufExtract_token(first_tok, vset, 0, ',');
561 StrBufRemove_token(vset, 0, ',');
563 if (StrBufNum_tokens(first_tok, ':') > 1) {
564 StrBufRemove_token(first_tok, 0, ':');
568 new_set = NewStrBuf();
569 StrBufAppendBufPlain(new_set, HKEY("1:"), 0);
570 StrBufAppendBuf(new_set, first_tok, 0);
571 StrBufAppendBufPlain(new_set, HKEY(":"), 0);
572 StrBufAppendBuf(new_set, vset, 0);
575 FreeStrBuf(&first_tok);
579 CtdlLogPrintf(CTDL_DEBUG, " after update: %s\n", ChrPtr(vset));
581 /* Decide which message set we're manipulating */
583 case ctdlsetseen_seen:
584 safestrncpy(vbuf.v_seen, ChrPtr(vset), sizeof vbuf.v_seen);
586 case ctdlsetseen_answered:
587 safestrncpy(vbuf.v_answered, ChrPtr(vset), sizeof vbuf.v_answered);
593 CtdlSetRelationship(&vbuf, which_user, which_room);
599 * API function to perform an operation for each qualifying message in the
600 * current room. (Returns the number of messages processed.)
602 int CtdlForEachMessage(int mode, long ref, char *search_string,
604 struct CtdlMessage *compare,
605 ForEachMsgCallback CallBack,
611 struct cdbdata *cdbfr;
612 long *msglist = NULL;
614 int num_processed = 0;
617 struct CtdlMessage *msg = NULL;
620 int printed_lastold = 0;
621 int num_search_msgs = 0;
622 long *search_msgs = NULL;
624 int need_to_free_re = 0;
627 if ((content_type) && (!IsEmptyStr(content_type))) {
628 regcomp(&re, content_type, 0);
632 /* Learn about the user and room in question */
633 CtdlGetUser(&CC->user, CC->curr_user);
634 CtdlGetRelationship(&vbuf, &CC->user, &CC->room);
636 /* Load the message list */
637 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
639 msglist = (long *) cdbfr->ptr;
640 num_msgs = cdbfr->len / sizeof(long);
642 if (need_to_free_re) regfree(&re);
643 return 0; /* No messages at all? No further action. */
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 cdb_free(cdbfr); /* Clean up */
778 if (need_to_free_re) regfree(&re);
779 return num_processed;
785 * cmd_msgs() - get list of message #'s in this room
786 * implements the MSGS server command using CtdlForEachMessage()
788 void cmd_msgs(char *cmdbuf)
797 int with_template = 0;
798 struct CtdlMessage *template = NULL;
799 char search_string[1024];
800 ForEachMsgCallback CallBack;
802 extract_token(which, cmdbuf, 0, '|', sizeof which);
803 cm_ref = extract_int(cmdbuf, 1);
804 extract_token(search_string, cmdbuf, 1, '|', sizeof search_string);
805 with_template = extract_int(cmdbuf, 2);
806 switch (extract_int(cmdbuf, 3))
810 CallBack = simple_listing;
813 CallBack = headers_listing;
816 CallBack = headers_euid;
821 if (!strncasecmp(which, "OLD", 3))
823 else if (!strncasecmp(which, "NEW", 3))
825 else if (!strncasecmp(which, "FIRST", 5))
827 else if (!strncasecmp(which, "LAST", 4))
829 else if (!strncasecmp(which, "GT", 2))
831 else if (!strncasecmp(which, "LT", 2))
833 else if (!strncasecmp(which, "SEARCH", 6))
838 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
839 cprintf("%d not logged in\n", ERROR + NOT_LOGGED_IN);
843 if ( (mode == MSGS_SEARCH) && (!config.c_enable_fulltext) ) {
844 cprintf("%d Full text index is not enabled on this server.\n",
845 ERROR + CMD_NOT_SUPPORTED);
851 cprintf("%d Send template then receive message list\n",
853 template = (struct CtdlMessage *)
854 malloc(sizeof(struct CtdlMessage));
855 memset(template, 0, sizeof(struct CtdlMessage));
856 template->cm_magic = CTDLMESSAGE_MAGIC;
857 template->cm_anon_type = MES_NORMAL;
859 while(client_getln(buf, sizeof buf) >= 0 && strcmp(buf,"000")) {
860 extract_token(tfield, buf, 0, '|', sizeof tfield);
861 extract_token(tvalue, buf, 1, '|', sizeof tvalue);
862 for (i='A'; i<='Z'; ++i) if (msgkeys[i]!=NULL) {
863 if (!strcasecmp(tfield, msgkeys[i])) {
864 template->cm_fields[i] =
872 cprintf("%d \n", LISTING_FOLLOWS);
875 CtdlForEachMessage(mode,
876 ( (mode == MSGS_SEARCH) ? 0 : cm_ref ),
877 ( (mode == MSGS_SEARCH) ? search_string : NULL ),
882 if (template != NULL) CtdlFreeMessage(template);
890 * help_subst() - support routine for help file viewer
892 void help_subst(char *strbuf, char *source, char *dest)
897 while (p = pattern2(strbuf, source), (p >= 0)) {
898 strcpy(workbuf, &strbuf[p + strlen(source)]);
899 strcpy(&strbuf[p], dest);
900 strcat(strbuf, workbuf);
905 void do_help_subst(char *buffer)
909 help_subst(buffer, "^nodename", config.c_nodename);
910 help_subst(buffer, "^humannode", config.c_humannode);
911 help_subst(buffer, "^fqdn", config.c_fqdn);
912 help_subst(buffer, "^username", CC->user.fullname);
913 snprintf(buf2, sizeof buf2, "%ld", CC->user.usernum);
914 help_subst(buffer, "^usernum", buf2);
915 help_subst(buffer, "^sysadm", config.c_sysadm);
916 help_subst(buffer, "^variantname", CITADEL);
917 snprintf(buf2, sizeof buf2, "%d", config.c_maxsessions);
918 help_subst(buffer, "^maxsessions", buf2);
919 help_subst(buffer, "^bbsdir", ctdl_message_dir);
925 * memfmout() - Citadel text formatter and paginator.
926 * Although the original purpose of this routine was to format
927 * text to the reader's screen width, all we're really using it
928 * for here is to format text out to 80 columns before sending it
929 * to the client. The client software may reformat it again.
932 char *mptr, /* where are we going to get our text from? */
933 const char *nl /* string to terminate lines with */
936 unsigned char ch = 0;
943 while (ch=*(mptr++), ch != 0) {
946 client_write(outbuf, len);
948 client_write(nl, nllen);
951 else if (ch == '\r') {
952 /* Ignore carriage returns. Newlines are always LF or CRLF but never CR. */
954 else if (isspace(ch)) {
955 if (column > 72) { /* Beyond 72 columns, break on the next space */
956 client_write(outbuf, len);
958 client_write(nl, nllen);
969 if (column > 1000) { /* Beyond 1000 columns, break anywhere */
970 client_write(outbuf, len);
972 client_write(nl, nllen);
978 client_write(outbuf, len);
980 client_write(nl, nllen);
988 * Callback function for mime parser that simply lists the part
990 void list_this_part(char *name, char *filename, char *partnum, char *disp,
991 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
992 char *cbid, void *cbuserdata)
996 ma = (struct ma_info *)cbuserdata;
997 if (ma->is_ma == 0) {
998 cprintf("part=%s|%s|%s|%s|%s|%ld|%s|%s\n",
1011 * Callback function for multipart prefix
1013 void list_this_pref(char *name, char *filename, char *partnum, char *disp,
1014 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1015 char *cbid, void *cbuserdata)
1019 ma = (struct ma_info *)cbuserdata;
1020 if (!strcasecmp(cbtype, "multipart/alternative")) {
1024 if (ma->is_ma == 0) {
1025 cprintf("pref=%s|%s\n", partnum, cbtype);
1030 * Callback function for multipart sufffix
1032 void list_this_suff(char *name, char *filename, char *partnum, char *disp,
1033 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1034 char *cbid, void *cbuserdata)
1038 ma = (struct ma_info *)cbuserdata;
1039 if (ma->is_ma == 0) {
1040 cprintf("suff=%s|%s\n", partnum, cbtype);
1042 if (!strcasecmp(cbtype, "multipart/alternative")) {
1049 * Callback function for mime parser that opens a section for downloading
1051 void mime_download(char *name, char *filename, char *partnum, char *disp,
1052 void *content, char *cbtype, char *cbcharset, size_t length,
1053 char *encoding, char *cbid, void *cbuserdata)
1057 /* Silently go away if there's already a download open. */
1058 if (CC->download_fp != NULL)
1062 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1063 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1065 CC->download_fp = tmpfile();
1066 if (CC->download_fp == NULL)
1069 rv = fwrite(content, length, 1, CC->download_fp);
1070 fflush(CC->download_fp);
1071 rewind(CC->download_fp);
1073 OpenCmdResult(filename, cbtype);
1080 * Callback function for mime parser that outputs a section all at once.
1081 * We can specify the desired section by part number *or* content-id.
1083 void mime_spew_section(char *name, char *filename, char *partnum, char *disp,
1084 void *content, char *cbtype, char *cbcharset, size_t length,
1085 char *encoding, char *cbid, void *cbuserdata)
1087 int *found_it = (int *)cbuserdata;
1090 (!IsEmptyStr(partnum) && (!strcasecmp(CC->download_desired_section, partnum)))
1091 || (!IsEmptyStr(cbid) && (!strcasecmp(CC->download_desired_section, cbid)))
1094 cprintf("%d %d|-1|%s|%s|%s\n",
1101 client_write(content, length);
1108 * Load a message from disk into memory.
1109 * This is used by CtdlOutputMsg() and other fetch functions.
1111 * NOTE: Caller is responsible for freeing the returned CtdlMessage struct
1112 * using the CtdlMessageFree() function.
1114 struct CtdlMessage *CtdlFetchMessage(long msgnum, int with_body)
1116 struct cdbdata *dmsgtext;
1117 struct CtdlMessage *ret = NULL;
1121 cit_uint8_t field_header;
1123 CtdlLogPrintf(CTDL_DEBUG, "CtdlFetchMessage(%ld, %d)\n", msgnum, with_body);
1125 dmsgtext = cdb_fetch(CDB_MSGMAIN, &msgnum, sizeof(long));
1126 if (dmsgtext == NULL) {
1129 mptr = dmsgtext->ptr;
1130 upper_bound = mptr + dmsgtext->len;
1132 /* Parse the three bytes that begin EVERY message on disk.
1133 * The first is always 0xFF, the on-disk magic number.
1134 * The second is the anonymous/public type byte.
1135 * The third is the format type byte (vari, fixed, or MIME).
1139 CtdlLogPrintf(CTDL_ERR, "Message %ld appears to be corrupted.\n", msgnum);
1143 ret = (struct CtdlMessage *) malloc(sizeof(struct CtdlMessage));
1144 memset(ret, 0, sizeof(struct CtdlMessage));
1146 ret->cm_magic = CTDLMESSAGE_MAGIC;
1147 ret->cm_anon_type = *mptr++; /* Anon type byte */
1148 ret->cm_format_type = *mptr++; /* Format type byte */
1151 * The rest is zero or more arbitrary fields. Load them in.
1152 * We're done when we encounter either a zero-length field or
1153 * have just processed the 'M' (message text) field.
1156 if (mptr >= upper_bound) {
1159 field_header = *mptr++;
1160 ret->cm_fields[field_header] = strdup(mptr);
1162 while (*mptr++ != 0); /* advance to next field */
1164 } while ((mptr < upper_bound) && (field_header != 'M'));
1168 /* Always make sure there's something in the msg text field. If
1169 * it's NULL, the message text is most likely stored separately,
1170 * so go ahead and fetch that. Failing that, just set a dummy
1171 * body so other code doesn't barf.
1173 if ( (ret->cm_fields['M'] == NULL) && (with_body) ) {
1174 dmsgtext = cdb_fetch(CDB_BIGMSGS, &msgnum, sizeof(long));
1175 if (dmsgtext != NULL) {
1176 ret->cm_fields['M'] = dmsgtext->ptr;
1177 dmsgtext->ptr = NULL;
1181 if (ret->cm_fields['M'] == NULL) {
1182 ret->cm_fields['M'] = strdup("\r\n\r\n (no text)\r\n");
1185 /* Perform "before read" hooks (aborting if any return nonzero) */
1186 if (PerformMessageHooks(ret, EVT_BEFOREREAD) > 0) {
1187 CtdlFreeMessage(ret);
1196 * Returns 1 if the supplied pointer points to a valid Citadel message.
1197 * If the pointer is NULL or the magic number check fails, returns 0.
1199 int is_valid_message(struct CtdlMessage *msg) {
1202 if ((msg->cm_magic) != CTDLMESSAGE_MAGIC) {
1203 CtdlLogPrintf(CTDL_WARNING, "is_valid_message() -- self-check failed\n");
1211 * 'Destructor' for struct CtdlMessage
1213 void CtdlFreeMessage(struct CtdlMessage *msg)
1217 if (is_valid_message(msg) == 0)
1219 if (msg != NULL) free (msg);
1223 for (i = 0; i < 256; ++i)
1224 if (msg->cm_fields[i] != NULL) {
1225 free(msg->cm_fields[i]);
1228 msg->cm_magic = 0; /* just in case */
1234 * Pre callback function for multipart/alternative
1236 * NOTE: this differs from the standard behavior for a reason. Normally when
1237 * displaying multipart/alternative you want to show the _last_ usable
1238 * format in the message. Here we show the _first_ one, because it's
1239 * usually text/plain. Since this set of functions is designed for text
1240 * output to non-MIME-aware clients, this is the desired behavior.
1243 void fixed_output_pre(char *name, char *filename, char *partnum, char *disp,
1244 void *content, char *cbtype, char *cbcharset, size_t length, char *encoding,
1245 char *cbid, void *cbuserdata)
1249 ma = (struct ma_info *)cbuserdata;
1250 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_pre() type=<%s>\n", cbtype);
1251 if (!strcasecmp(cbtype, "multipart/alternative")) {
1255 if (!strcasecmp(cbtype, "message/rfc822")) {
1261 * Post callback function for multipart/alternative
1263 void fixed_output_post(char *name, char *filename, char *partnum, char *disp,
1264 void *content, char *cbtype, char *cbcharset, size_t length,
1265 char *encoding, char *cbid, void *cbuserdata)
1269 ma = (struct ma_info *)cbuserdata;
1270 CtdlLogPrintf(CTDL_DEBUG, "fixed_output_post() type=<%s>\n", cbtype);
1271 if (!strcasecmp(cbtype, "multipart/alternative")) {
1275 if (!strcasecmp(cbtype, "message/rfc822")) {
1281 * Inline callback function for mime parser that wants to display text
1283 void fixed_output(char *name, char *filename, char *partnum, char *disp,
1284 void *content, char *cbtype, char *cbcharset, size_t length,
1285 char *encoding, char *cbid, void *cbuserdata)
1292 ma = (struct ma_info *)cbuserdata;
1294 CtdlLogPrintf(CTDL_DEBUG,
1295 "fixed_output() part %s: %s (%s) (%ld bytes)\n",
1296 partnum, filename, cbtype, (long)length);
1299 * If we're in the middle of a multipart/alternative scope and
1300 * we've already printed another section, skip this one.
1302 if ( (ma->is_ma) && (ma->did_print) ) {
1303 CtdlLogPrintf(CTDL_DEBUG, "Skipping part %s (%s)\n", partnum, cbtype);
1308 if ( (!strcasecmp(cbtype, "text/plain"))
1309 || (IsEmptyStr(cbtype)) ) {
1312 client_write(wptr, length);
1313 if (wptr[length-1] != '\n') {
1320 if (!strcasecmp(cbtype, "text/html")) {
1321 ptr = html_to_ascii(content, length, 80, 0);
1323 client_write(ptr, wlen);
1324 if (ptr[wlen-1] != '\n') {
1331 if (ma->use_fo_hooks) {
1332 if (PerformFixedOutputHooks(cbtype, content, length)) {
1333 /* above function returns nonzero if it handled the part */
1338 if (strncasecmp(cbtype, "multipart/", 10)) {
1339 cprintf("Part %s: %s (%s) (%ld bytes)\r\n",
1340 partnum, filename, cbtype, (long)length);
1346 * The client is elegant and sophisticated and wants to be choosy about
1347 * MIME content types, so figure out which multipart/alternative part
1348 * we're going to send.
1350 * We use a system of weights. When we find a part that matches one of the
1351 * MIME types we've declared as preferential, we can store it in ma->chosen_part
1352 * and then set ma->chosen_pref to that MIME type's position in our preference
1353 * list. If we then hit another match, we only replace the first match if
1354 * the preference value is lower.
1356 void choose_preferred(char *name, char *filename, char *partnum, char *disp,
1357 void *content, char *cbtype, char *cbcharset, size_t length,
1358 char *encoding, char *cbid, void *cbuserdata)
1364 ma = (struct ma_info *)cbuserdata;
1366 // NOTE: REMOVING THIS CONDITIONAL FIXES BUG 220
1367 // http://bugzilla.citadel.org/show_bug.cgi?id=220
1368 // I don't know if there are any side effects! Please TEST TEST TEST
1369 //if (ma->is_ma > 0) {
1371 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1372 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1373 if ( (!strcasecmp(buf, cbtype)) && (!ma->freeze) ) {
1374 if (i < ma->chosen_pref) {
1375 CtdlLogPrintf(CTDL_DEBUG, "Setting chosen part: <%s>\n", partnum);
1376 safestrncpy(ma->chosen_part, partnum, sizeof ma->chosen_part);
1377 ma->chosen_pref = i;
1384 * Now that we've chosen our preferred part, output it.
1386 void output_preferred(char *name, char *filename, char *partnum, char *disp,
1387 void *content, char *cbtype, char *cbcharset, size_t length,
1388 char *encoding, char *cbid, void *cbuserdata)
1392 int add_newline = 0;
1396 ma = (struct ma_info *)cbuserdata;
1398 /* This is not the MIME part you're looking for... */
1399 if (strcasecmp(partnum, ma->chosen_part)) return;
1401 /* If the content-type of this part is in our preferred formats
1402 * list, we can simply output it verbatim.
1404 for (i=0; i<num_tokens(CC->preferred_formats, '|'); ++i) {
1405 extract_token(buf, CC->preferred_formats, i, '|', sizeof buf);
1406 if (!strcasecmp(buf, cbtype)) {
1407 /* Yeah! Go! W00t!! */
1409 text_content = (char *)content;
1410 if (text_content[length-1] != '\n') {
1413 cprintf("Content-type: %s", cbtype);
1414 if (!IsEmptyStr(cbcharset)) {
1415 cprintf("; charset=%s", cbcharset);
1417 cprintf("\nContent-length: %d\n",
1418 (int)(length + add_newline) );
1419 if (!IsEmptyStr(encoding)) {
1420 cprintf("Content-transfer-encoding: %s\n", encoding);
1423 cprintf("Content-transfer-encoding: 7bit\n");
1425 cprintf("X-Citadel-MSG4-Partnum: %s\n", partnum);
1427 client_write(content, length);
1428 if (add_newline) cprintf("\n");
1433 /* No translations required or possible: output as text/plain */
1434 cprintf("Content-type: text/plain\n\n");
1435 fixed_output(name, filename, partnum, disp, content, cbtype, cbcharset,
1436 length, encoding, cbid, cbuserdata);
1441 char desired_section[64];
1448 * Callback function for
1450 void extract_encapsulated_message(char *name, char *filename, char *partnum, char *disp,
1451 void *content, char *cbtype, char *cbcharset, size_t length,
1452 char *encoding, char *cbid, void *cbuserdata)
1454 struct encapmsg *encap;
1456 encap = (struct encapmsg *)cbuserdata;
1458 /* Only proceed if this is the desired section... */
1459 if (!strcasecmp(encap->desired_section, partnum)) {
1460 encap->msglen = length;
1461 encap->msg = malloc(length + 2);
1462 memcpy(encap->msg, content, length);
1473 int CtdlDoIHavePermissionToReadMessagesInThisRoom(void) {
1474 if ((!(CC->logged_in)) && (!(CC->internal_pgm))) {
1475 return(om_not_logged_in);
1482 * Get a message off disk. (returns om_* values found in msgbase.h)
1485 int CtdlOutputMsg(long msg_num, /* message number (local) to fetch */
1486 int mode, /* how would you like that message? */
1487 int headers_only, /* eschew the message body? */
1488 int do_proto, /* do Citadel protocol responses? */
1489 int crlf, /* Use CRLF newlines instead of LF? */
1490 char *section, /* NULL or a message/rfc822 section */
1491 int flags /* various flags; see msgbase.h */
1493 struct CtdlMessage *TheMessage = NULL;
1494 int retcode = om_no_such_msg;
1495 struct encapmsg encap;
1498 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputMsg(msgnum=%ld, mode=%d, section=%s)\n",
1500 (section ? section : "<>")
1503 r = CtdlDoIHavePermissionToReadMessagesInThisRoom();
1506 if (r == om_not_logged_in) {
1507 cprintf("%d Not logged in.\n", ERROR + NOT_LOGGED_IN);
1510 cprintf("%d An unknown error has occurred.\n", ERROR);
1516 /* FIXME: check message id against msglist for this room */
1519 * Fetch the message from disk. If we're in HEADERS_FAST mode,
1520 * request that we don't even bother loading the body into memory.
1522 if (headers_only == HEADERS_FAST) {
1523 TheMessage = CtdlFetchMessage(msg_num, 0);
1526 TheMessage = CtdlFetchMessage(msg_num, 1);
1529 if (TheMessage == NULL) {
1530 if (do_proto) cprintf("%d Can't locate msg %ld on disk\n",
1531 ERROR + MESSAGE_NOT_FOUND, msg_num);
1532 return(om_no_such_msg);
1535 /* Here is the weird form of this command, to process only an
1536 * encapsulated message/rfc822 section.
1538 if (section) if (!IsEmptyStr(section)) if (strcmp(section, "0")) {
1539 memset(&encap, 0, sizeof encap);
1540 safestrncpy(encap.desired_section, section, sizeof encap.desired_section);
1541 mime_parser(TheMessage->cm_fields['M'],
1543 *extract_encapsulated_message,
1544 NULL, NULL, (void *)&encap, 0
1546 CtdlFreeMessage(TheMessage);
1550 encap.msg[encap.msglen] = 0;
1551 TheMessage = convert_internet_message(encap.msg);
1552 encap.msg = NULL; /* no free() here, TheMessage owns it now */
1554 /* Now we let it fall through to the bottom of this
1555 * function, because TheMessage now contains the
1556 * encapsulated message instead of the top-level
1557 * message. Isn't that neat?
1562 if (do_proto) cprintf("%d msg %ld has no part %s\n",
1563 ERROR + MESSAGE_NOT_FOUND, msg_num, section);
1564 retcode = om_no_such_msg;
1569 /* Ok, output the message now */
1570 retcode = CtdlOutputPreLoadedMsg(TheMessage, mode, headers_only, do_proto, crlf, flags);
1571 CtdlFreeMessage(TheMessage);
1577 char *qp_encode_email_addrs(char *source)
1579 char *user, *node, *name;
1580 const char headerStr[] = "=?UTF-8?Q?";
1584 int need_to_encode = 0;
1590 long nAddrPtrMax = 50;
1595 if (source == NULL) return source;
1596 if (IsEmptyStr(source)) return source;
1598 CtdlLogPrintf(CTDL_DEBUG, "qp_encode_email_addrs: [%s]\n", source);
1600 AddrPtr = malloc (sizeof (long) * nAddrPtrMax);
1601 AddrUtf8 = malloc (sizeof (long) * nAddrPtrMax);
1602 memset(AddrUtf8, 0, sizeof (long) * nAddrPtrMax);
1605 while (!IsEmptyStr (&source[i])) {
1606 if (nColons >= nAddrPtrMax){
1609 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1610 memcpy (ptr, AddrPtr, sizeof (long) * nAddrPtrMax);
1611 free (AddrPtr), AddrPtr = ptr;
1613 ptr = (long *) malloc(sizeof (long) * nAddrPtrMax * 2);
1614 memset(&ptr[nAddrPtrMax], 0,
1615 sizeof (long) * nAddrPtrMax);
1617 memcpy (ptr, AddrUtf8, sizeof (long) * nAddrPtrMax);
1618 free (AddrUtf8), AddrUtf8 = ptr;
1621 if (((unsigned char) source[i] < 32) ||
1622 ((unsigned char) source[i] > 126)) {
1624 AddrUtf8[nColons] = 1;
1626 if (source[i] == '"')
1627 InQuotes = !InQuotes;
1628 if (!InQuotes && source[i] == ',') {
1629 AddrPtr[nColons] = i;
1634 if (need_to_encode == 0) {
1641 EncodedMaxLen = nColons * (sizeof(headerStr) + 3) + SourceLen * 3;
1642 Encoded = (char*) malloc (EncodedMaxLen);
1644 for (i = 0; i < nColons; i++)
1645 source[AddrPtr[i]++] = '\0';
1646 /* TODO: if libidn, this might get larger*/
1647 user = malloc(SourceLen + 1);
1648 node = malloc(SourceLen + 1);
1649 name = malloc(SourceLen + 1);
1653 for (i = 0; i < nColons && nPtr != NULL; i++) {
1654 nmax = EncodedMaxLen - (nPtr - Encoded);
1656 process_rfc822_addr(&source[AddrPtr[i]],
1660 /* TODO: libIDN here ! */
1661 if (IsEmptyStr(name)) {
1662 n = snprintf(nPtr, nmax,
1663 (i==0)?"%s@%s" : ",%s@%s",
1667 EncodedName = rfc2047encode(name, strlen(name));
1668 n = snprintf(nPtr, nmax,
1669 (i==0)?"%s <%s@%s>" : ",%s <%s@%s>",
1670 EncodedName, user, node);
1675 n = snprintf(nPtr, nmax,
1676 (i==0)?"%s" : ",%s",
1677 &source[AddrPtr[i]]);
1683 ptr = (char*) malloc(EncodedMaxLen * 2);
1684 memcpy(ptr, Encoded, EncodedMaxLen);
1685 nnPtr = ptr + (nPtr - Encoded), nPtr = nnPtr;
1686 free(Encoded), Encoded = ptr;
1688 i--; /* do it once more with properly lengthened buffer */
1691 for (i = 0; i < nColons; i++)
1692 source[--AddrPtr[i]] = ',';
1703 /* If the last item in a list of recipients was truncated to a partial address,
1704 * remove it completely in order to avoid choking libSieve
1706 void sanitize_truncated_recipient(char *str)
1709 if (num_tokens(str, ',') < 2) return;
1711 int len = strlen(str);
1712 if (len < 900) return;
1713 if (len > 998) str[998] = 0;
1715 char *cptr = strrchr(str, ',');
1718 char *lptr = strchr(cptr, '<');
1719 char *rptr = strchr(cptr, '>');
1721 if ( (lptr) && (rptr) && (rptr > lptr) ) return;
1727 void OutputCtdlMsgHeaders(
1728 struct CtdlMessage *TheMessage,
1729 int do_proto) /* do Citadel protocol responses? */
1735 char display_name[256];
1737 /* begin header processing loop for Citadel message format */
1738 safestrncpy(display_name, "<unknown>", sizeof display_name);
1739 if (TheMessage->cm_fields['A']) {
1740 strcpy(buf, TheMessage->cm_fields['A']);
1741 if (TheMessage->cm_anon_type == MES_ANONONLY) {
1742 safestrncpy(display_name, "****", sizeof display_name);
1744 else if (TheMessage->cm_anon_type == MES_ANONOPT) {
1745 safestrncpy(display_name, "anonymous", sizeof display_name);
1748 safestrncpy(display_name, buf, sizeof display_name);
1750 if ((is_room_aide())
1751 && ((TheMessage->cm_anon_type == MES_ANONONLY)
1752 || (TheMessage->cm_anon_type == MES_ANONOPT))) {
1753 size_t tmp = strlen(display_name);
1754 snprintf(&display_name[tmp],
1755 sizeof display_name - tmp,
1760 /* Don't show Internet address for users on the
1761 * local Citadel network.
1764 if (TheMessage->cm_fields['N'] != NULL)
1765 if (!IsEmptyStr(TheMessage->cm_fields['N']))
1766 if (haschar(TheMessage->cm_fields['N'], '.') == 0) {
1770 /* Now spew the header fields in the order we like them. */
1771 n = safestrncpy(allkeys, FORDER, sizeof allkeys);
1772 for (i=0; i<n; ++i) {
1773 k = (int) allkeys[i];
1775 if ( (TheMessage->cm_fields[k] != NULL)
1776 && (msgkeys[k] != NULL) ) {
1777 if ((k == 'V') || (k == 'R') || (k == 'Y')) {
1778 sanitize_truncated_recipient(TheMessage->cm_fields[k]);
1781 if (do_proto) cprintf("%s=%s\n",
1785 else if ((k == 'F') && (suppress_f)) {
1788 /* Masquerade display name if needed */
1790 if (do_proto) cprintf("%s=%s\n",
1792 TheMessage->cm_fields[k]
1801 void OutputRFC822MsgHeaders(
1802 struct CtdlMessage *TheMessage,
1803 int flags, /* should the bessage be exported clean */
1805 char *mid, long sizeof_mid,
1806 char *suser, long sizeof_suser,
1807 char *luser, long sizeof_luser,
1808 char *fuser, long sizeof_fuser,
1809 char *snode, long sizeof_snode)
1811 char datestamp[100];
1812 int subject_found = 0;
1819 for (i = 0; i < 256; ++i) {
1820 if (TheMessage->cm_fields[i]) {
1821 mptr = mpptr = TheMessage->cm_fields[i];
1824 safestrncpy(luser, mptr, sizeof_luser);
1825 safestrncpy(suser, mptr, sizeof_suser);
1827 else if (i == 'Y') {
1828 if ((flags & QP_EADDR) != 0) {
1829 mptr = qp_encode_email_addrs(mptr);
1831 sanitize_truncated_recipient(mptr);
1832 cprintf("CC: %s%s", mptr, nl);
1834 else if (i == 'P') {
1835 cprintf("Return-Path: %s%s", mptr, nl);
1837 else if (i == 'L') {
1838 cprintf("List-ID: %s%s", mptr, nl);
1840 else if (i == 'V') {
1841 if ((flags & QP_EADDR) != 0)
1842 mptr = qp_encode_email_addrs(mptr);
1844 while ((*hptr != '\0') && isspace(*hptr))
1846 if (!IsEmptyStr(hptr))
1847 cprintf("Envelope-To: %s%s", hptr, nl);
1849 else if (i == 'U') {
1850 cprintf("Subject: %s%s", mptr, nl);
1854 safestrncpy(mid, mptr, sizeof_mid); /// TODO: detect @ here and copy @nodename in if not found.
1856 safestrncpy(fuser, mptr, sizeof_fuser);
1857 /* else if (i == 'O')
1858 cprintf("X-Citadel-Room: %s%s",
1861 safestrncpy(snode, mptr, sizeof_snode);
1864 if (haschar(mptr, '@') == 0)
1866 sanitize_truncated_recipient(mptr);
1867 cprintf("To: %s@%s", mptr, config.c_fqdn);
1872 if ((flags & QP_EADDR) != 0) {
1873 mptr = qp_encode_email_addrs(mptr);
1875 sanitize_truncated_recipient(mptr);
1876 cprintf("To: %s", mptr);
1880 else if (i == 'T') {
1881 datestring(datestamp, sizeof datestamp,
1882 atol(mptr), DATESTRING_RFC822);
1883 cprintf("Date: %s%s", datestamp, nl);
1885 else if (i == 'W') {
1886 cprintf("References: ");
1887 k = num_tokens(mptr, '|');
1888 for (j=0; j<k; ++j) {
1889 extract_token(buf, mptr, j, '|', sizeof buf);
1890 cprintf("<%s>", buf);
1899 else if (i == 'K') {
1901 while ((*hptr != '\0') && isspace(*hptr))
1903 if (!IsEmptyStr(hptr))
1904 cprintf("Reply-To: %s%s", mptr, nl);
1910 if (subject_found == 0) {
1911 cprintf("Subject: (no subject)%s", nl);
1916 void Dump_RFC822HeadersBody(
1917 struct CtdlMessage *TheMessage,
1918 int headers_only, /* eschew the message body? */
1919 int flags, /* should the bessage be exported clean? */
1923 cit_uint8_t prev_ch;
1925 const char *StartOfText = StrBufNOTNULL;
1928 int nllen = strlen(nl);
1931 mptr = TheMessage->cm_fields['M'];
1935 while (*mptr != '\0') {
1936 if (*mptr == '\r') {
1943 eoh = (*(mptr+1) == '\r') && (*(mptr+2) == '\n');
1945 eoh = *(mptr+1) == '\n';
1949 StartOfText = strchr(StartOfText, '\n');
1950 StartOfText = strchr(StartOfText, '\n');
1953 if (((headers_only == HEADERS_NONE) && (mptr >= StartOfText)) ||
1954 ((headers_only == HEADERS_ONLY) && (mptr < StartOfText)) ||
1955 ((headers_only != HEADERS_NONE) &&
1956 (headers_only != HEADERS_ONLY))
1958 if (*mptr == '\n') {
1959 memcpy(&outbuf[outlen], nl, nllen);
1961 outbuf[outlen] = '\0';
1964 outbuf[outlen++] = *mptr;
1968 if (flags & ESC_DOT)
1970 if ((prev_ch == '\n') &&
1972 ((*(mptr+1) == '\r') || (*(mptr+1) == '\n')))
1974 outbuf[outlen++] = '.';
1979 if (outlen > 1000) {
1980 client_write(outbuf, outlen);
1985 client_write(outbuf, outlen);
1992 /* If the format type on disk is 1 (fixed-format), then we want
1993 * everything to be output completely literally ... regardless of
1994 * what message transfer format is in use.
1996 void DumpFormatFixed(
1997 struct CtdlMessage *TheMessage,
1998 int mode, /* how would you like that message? */
2005 int nllen = strlen (nl);
2008 mptr = TheMessage->cm_fields['M'];
2010 if (mode == MT_MIME) {
2011 cprintf("Content-type: text/plain\n\n");
2015 while (ch = *mptr++, ch > 0) {
2019 if ((buflen > 250) && (!xlline)){
2023 while ((buflen > 0) &&
2024 (!isspace(buf[buflen])))
2030 mptr -= tbuflen - buflen;
2035 /* if we reach the outer bounds of our buffer,
2036 abort without respect what whe purge. */
2039 (buflen > SIZ - nllen - 2)))
2043 memcpy (&buf[buflen], nl, nllen);
2047 client_write(buf, buflen);
2057 if (!IsEmptyStr(buf))
2058 cprintf("%s%s", buf, nl);
2062 * Get a message off disk. (returns om_* values found in msgbase.h)
2064 int CtdlOutputPreLoadedMsg(
2065 struct CtdlMessage *TheMessage,
2066 int mode, /* how would you like that message? */
2067 int headers_only, /* eschew the message body? */
2068 int do_proto, /* do Citadel protocol responses? */
2069 int crlf, /* Use CRLF newlines instead of LF? */
2070 int flags /* should the bessage be exported clean? */
2074 const char *nl; /* newline string */
2077 /* Buffers needed for RFC822 translation. These are all filled
2078 * using functions that are bounds-checked, and therefore we can
2079 * make them substantially smaller than SIZ.
2087 CtdlLogPrintf(CTDL_DEBUG, "CtdlOutputPreLoadedMsg(TheMessage=%s, %d, %d, %d, %d\n",
2088 ((TheMessage == NULL) ? "NULL" : "not null"),
2089 mode, headers_only, do_proto, crlf);
2091 strcpy(mid, "unknown");
2092 nl = (crlf ? "\r\n" : "\n");
2094 if (!is_valid_message(TheMessage)) {
2095 CtdlLogPrintf(CTDL_ERR,
2096 "ERROR: invalid preloaded message for output\n");
2098 return(om_no_such_msg);
2101 /* Suppress envelope recipients if required to avoid disclosing BCC addresses.
2102 * Pad it with spaces in order to avoid changing the RFC822 length of the message.
2104 if ( (flags & SUPPRESS_ENV_TO) && (TheMessage->cm_fields['V'] != NULL) ) {
2105 memset(TheMessage->cm_fields['V'], ' ', strlen(TheMessage->cm_fields['V']));
2108 /* Are we downloading a MIME component? */
2109 if (mode == MT_DOWNLOAD) {
2110 if (TheMessage->cm_format_type != FMT_RFC822) {
2112 cprintf("%d This is not a MIME message.\n",
2113 ERROR + ILLEGAL_VALUE);
2114 } else if (CC->download_fp != NULL) {
2115 if (do_proto) cprintf(
2116 "%d You already have a download open.\n",
2117 ERROR + RESOURCE_BUSY);
2119 /* Parse the message text component */
2120 mptr = TheMessage->cm_fields['M'];
2121 mime_parser(mptr, NULL, *mime_download, NULL, NULL, NULL, 0);
2122 /* If there's no file open by this time, the requested
2123 * section wasn't found, so print an error
2125 if (CC->download_fp == NULL) {
2126 if (do_proto) cprintf(
2127 "%d Section %s not found.\n",
2128 ERROR + FILE_NOT_FOUND,
2129 CC->download_desired_section);
2132 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2135 /* MT_SPEW_SECTION is like MT_DOWNLOAD except it outputs the whole MIME part
2136 * in a single server operation instead of opening a download file.
2138 if (mode == MT_SPEW_SECTION) {
2139 if (TheMessage->cm_format_type != FMT_RFC822) {
2141 cprintf("%d This is not a MIME message.\n",
2142 ERROR + ILLEGAL_VALUE);
2144 /* Parse the message text component */
2147 mptr = TheMessage->cm_fields['M'];
2148 mime_parser(mptr, NULL, *mime_spew_section, NULL, NULL, (void *)&found_it, 0);
2149 /* If section wasn't found, print an error
2152 if (do_proto) cprintf(
2153 "%d Section %s not found.\n",
2154 ERROR + FILE_NOT_FOUND,
2155 CC->download_desired_section);
2158 return((CC->download_fp != NULL) ? om_ok : om_mime_error);
2161 /* now for the user-mode message reading loops */
2162 if (do_proto) cprintf("%d msg:\n", LISTING_FOLLOWS);
2164 /* Does the caller want to skip the headers? */
2165 if (headers_only == HEADERS_NONE) goto START_TEXT;
2167 /* Tell the client which format type we're using. */
2168 if ( (mode == MT_CITADEL) && (do_proto) ) {
2169 cprintf("type=%d\n", TheMessage->cm_format_type);
2172 /* nhdr=yes means that we're only displaying headers, no body */
2173 if ( (TheMessage->cm_anon_type == MES_ANONONLY)
2174 && ((mode == MT_CITADEL) || (mode == MT_MIME))
2177 cprintf("nhdr=yes\n");
2180 if ((mode == MT_CITADEL) || (mode == MT_MIME))
2181 OutputCtdlMsgHeaders(TheMessage, do_proto);
2184 /* begin header processing loop for RFC822 transfer format */
2188 strcpy(snode, NODENAME);
2189 if (mode == MT_RFC822)
2190 OutputRFC822MsgHeaders(
2195 suser, sizeof(suser),
2196 luser, sizeof(luser),
2197 fuser, sizeof(fuser),
2198 snode, sizeof(snode)
2202 for (i=0; !IsEmptyStr(&suser[i]); ++i) {
2203 suser[i] = tolower(suser[i]);
2204 if (!isalnum(suser[i])) suser[i]='_';
2207 if (mode == MT_RFC822) {
2208 if (!strcasecmp(snode, NODENAME)) {
2209 safestrncpy(snode, FQDN, sizeof snode);
2212 /* Construct a fun message id */
2213 cprintf("Message-ID: <%s", mid);/// todo: this possibly breaks threadding mails.
2214 if (strchr(mid, '@')==NULL) {
2215 cprintf("@%s", snode);
2219 if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONONLY)) {
2220 cprintf("From: \"----\" <x@x.org>%s", nl);
2222 else if (!is_room_aide() && (TheMessage->cm_anon_type == MES_ANONOPT)) {
2223 cprintf("From: \"anonymous\" <x@x.org>%s", nl);
2225 else if (!IsEmptyStr(fuser)) {
2226 cprintf("From: \"%s\" <%s>%s", luser, fuser, nl);
2229 cprintf("From: \"%s\" <%s@%s>%s", luser, suser, snode, nl);
2232 /* Blank line signifying RFC822 end-of-headers */
2233 if (TheMessage->cm_format_type != FMT_RFC822) {
2238 /* end header processing loop ... at this point, we're in the text */
2240 if (headers_only == HEADERS_FAST) goto DONE;
2242 /* Tell the client about the MIME parts in this message */
2243 if (TheMessage->cm_format_type == FMT_RFC822) {
2244 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2245 mptr = TheMessage->cm_fields['M'];
2246 memset(&ma, 0, sizeof(struct ma_info));
2247 mime_parser(mptr, NULL,
2248 (do_proto ? *list_this_part : NULL),
2249 (do_proto ? *list_this_pref : NULL),
2250 (do_proto ? *list_this_suff : NULL),
2253 else if (mode == MT_RFC822) { /* unparsed RFC822 dump */
2254 Dump_RFC822HeadersBody(
2263 if (headers_only == HEADERS_ONLY) {
2267 /* signify start of msg text */
2268 if ( (mode == MT_CITADEL) || (mode == MT_MIME) ) {
2269 if (do_proto) cprintf("text\n");
2272 if (TheMessage->cm_format_type == FMT_FIXED)
2275 mode, /* how would you like that message? */
2278 /* If the message on disk is format 0 (Citadel vari-format), we
2279 * output using the formatter at 80 columns. This is the final output
2280 * form if the transfer format is RFC822, but if the transfer format
2281 * is Citadel proprietary, it'll still work, because the indentation
2282 * for new paragraphs is correct and the client will reformat the
2283 * message to the reader's screen width.
2285 if (TheMessage->cm_format_type == FMT_CITADEL) {
2286 mptr = TheMessage->cm_fields['M'];
2288 if (mode == MT_MIME) {
2289 cprintf("Content-type: text/x-citadel-variformat\n\n");
2294 /* If the message on disk is format 4 (MIME), we've gotta hand it
2295 * off to the MIME parser. The client has already been told that
2296 * this message is format 1 (fixed format), so the callback function
2297 * we use will display those parts as-is.
2299 if (TheMessage->cm_format_type == FMT_RFC822) {
2300 memset(&ma, 0, sizeof(struct ma_info));
2302 if (mode == MT_MIME) {
2303 ma.use_fo_hooks = 0;
2304 strcpy(ma.chosen_part, "1");
2305 ma.chosen_pref = 9999;
2306 mime_parser(mptr, NULL,
2307 *choose_preferred, *fixed_output_pre,
2308 *fixed_output_post, (void *)&ma, 0);
2309 mime_parser(mptr, NULL,
2310 *output_preferred, NULL, NULL, (void *)&ma, CC->msg4_dont_decode);
2313 ma.use_fo_hooks = 1;
2314 mime_parser(mptr, NULL,
2315 *fixed_output, *fixed_output_pre,
2316 *fixed_output_post, (void *)&ma, 0);
2321 DONE: /* now we're done */
2322 if (do_proto) cprintf("000\n");
2328 * display a message (mode 0 - Citadel proprietary)
2330 void cmd_msg0(char *cmdbuf)
2333 int headers_only = HEADERS_ALL;
2335 msgid = extract_long(cmdbuf, 0);
2336 headers_only = extract_int(cmdbuf, 1);
2338 CtdlOutputMsg(msgid, MT_CITADEL, headers_only, 1, 0, NULL, 0);
2344 * display a message (mode 2 - RFC822)
2346 void cmd_msg2(char *cmdbuf)
2349 int headers_only = HEADERS_ALL;
2351 msgid = extract_long(cmdbuf, 0);
2352 headers_only = extract_int(cmdbuf, 1);
2354 CtdlOutputMsg(msgid, MT_RFC822, headers_only, 1, 1, NULL, 0);
2360 * display a message (mode 3 - IGnet raw format - internal programs only)
2362 void cmd_msg3(char *cmdbuf)
2365 struct CtdlMessage *msg = NULL;
2368 if (CC->internal_pgm == 0) {
2369 cprintf("%d This command is for internal programs only.\n",
2370 ERROR + HIGHER_ACCESS_REQUIRED);
2374 msgnum = extract_long(cmdbuf, 0);
2375 msg = CtdlFetchMessage(msgnum, 1);
2377 cprintf("%d Message %ld not found.\n",
2378 ERROR + MESSAGE_NOT_FOUND, msgnum);
2382 serialize_message(&smr, msg);
2383 CtdlFreeMessage(msg);
2386 cprintf("%d Unable to serialize message\n",
2387 ERROR + INTERNAL_ERROR);
2391 cprintf("%d %ld\n", BINARY_FOLLOWS, (long)smr.len);
2392 client_write((char *)smr.ser, (int)smr.len);
2399 * Display a message using MIME content types
2401 void cmd_msg4(char *cmdbuf)
2406 msgid = extract_long(cmdbuf, 0);
2407 extract_token(section, cmdbuf, 1, '|', sizeof section);
2408 CtdlOutputMsg(msgid, MT_MIME, 0, 1, 0, (section[0] ? section : NULL) , 0);
2414 * Client tells us its preferred message format(s)
2416 void cmd_msgp(char *cmdbuf)
2418 if (!strcasecmp(cmdbuf, "dont_decode")) {
2419 CC->msg4_dont_decode = 1;
2420 cprintf("%d MSG4 will not pre-decode messages.\n", CIT_OK);
2423 safestrncpy(CC->preferred_formats, cmdbuf, sizeof(CC->preferred_formats));
2424 cprintf("%d Preferred MIME formats have been set.\n", CIT_OK);
2430 * Open a component of a MIME message as a download file
2432 void cmd_opna(char *cmdbuf)
2435 char desired_section[128];
2437 msgid = extract_long(cmdbuf, 0);
2438 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2439 safestrncpy(CC->download_desired_section, desired_section,
2440 sizeof CC->download_desired_section);
2441 CtdlOutputMsg(msgid, MT_DOWNLOAD, 0, 1, 1, NULL, 0);
2446 * Open a component of a MIME message and transmit it all at once
2448 void cmd_dlat(char *cmdbuf)
2451 char desired_section[128];
2453 msgid = extract_long(cmdbuf, 0);
2454 extract_token(desired_section, cmdbuf, 1, '|', sizeof desired_section);
2455 safestrncpy(CC->download_desired_section, desired_section,
2456 sizeof CC->download_desired_section);
2457 CtdlOutputMsg(msgid, MT_SPEW_SECTION, 0, 1, 1, NULL, 0);
2462 * Save one or more message pointers into a specified room
2463 * (Returns 0 for success, nonzero for failure)
2464 * roomname may be NULL to use the current room
2466 * Note that the 'supplied_msg' field may be set to NULL, in which case
2467 * the message will be fetched from disk, by number, if we need to perform
2468 * replication checks. This adds an additional database read, so if the
2469 * caller already has the message in memory then it should be supplied. (Obviously
2470 * this mode of operation only works if we're saving a single message.)
2472 int CtdlSaveMsgPointersInRoom(char *roomname, long newmsgidlist[], int num_newmsgs,
2473 int do_repl_check, struct CtdlMessage *supplied_msg, int suppress_refcount_adj
2476 char hold_rm[ROOMNAMELEN];
2477 struct cdbdata *cdbfr;
2480 long highest_msg = 0L;
2483 struct CtdlMessage *msg = NULL;
2485 long *msgs_to_be_merged = NULL;
2486 int num_msgs_to_be_merged = 0;
2488 CtdlLogPrintf(CTDL_DEBUG,
2489 "CtdlSaveMsgPointersInRoom(room=%s, num_msgs=%d, repl=%d, suppress_rca=%d)\n",
2490 roomname, num_newmsgs, do_repl_check, suppress_refcount_adj
2493 strcpy(hold_rm, CC->room.QRname);
2496 if (newmsgidlist == NULL) return(ERROR + INTERNAL_ERROR);
2497 if (num_newmsgs < 1) return(ERROR + INTERNAL_ERROR);
2498 if (num_newmsgs > 1) supplied_msg = NULL;
2500 /* Now the regular stuff */
2501 if (CtdlGetRoomLock(&CC->room,
2502 ((roomname != NULL) ? roomname : CC->room.QRname) )
2504 CtdlLogPrintf(CTDL_ERR, "No such room <%s>\n", roomname);
2505 return(ERROR + ROOM_NOT_FOUND);
2509 msgs_to_be_merged = malloc(sizeof(long) * num_newmsgs);
2510 num_msgs_to_be_merged = 0;
2513 cdbfr = cdb_fetch(CDB_MSGLISTS, &CC->room.QRnumber, sizeof(long));
2514 if (cdbfr == NULL) {
2518 msglist = (long *) cdbfr->ptr;
2519 cdbfr->ptr = NULL; /* CtdlSaveMsgPointerInRoom() now owns this memory */
2520 num_msgs = cdbfr->len / sizeof(long);
2525 /* Create a list of msgid's which were supplied by the caller, but do
2526 * not already exist in the target room. It is absolutely taboo to
2527 * have more than one reference to the same message in a room.
2529 for (i=0; i<num_newmsgs; ++i) {
2531 if (num_msgs > 0) for (j=0; j<num_msgs; ++j) {
2532 if (msglist[j] == newmsgidlist[i]) {
2537 msgs_to_be_merged[num_msgs_to_be_merged++] = newmsgidlist[i];
2541 CtdlLogPrintf(9, "%d unique messages to be merged\n", num_msgs_to_be_merged);
2544 * Now merge the new messages
2546 msglist = realloc(msglist, (sizeof(long) * (num_msgs + num_msgs_to_be_merged)) );
2547 if (msglist == NULL) {
2548 CtdlLogPrintf(CTDL_ALERT, "ERROR: can't realloc message list!\n");
2550 memcpy(&msglist[num_msgs], msgs_to_be_merged, (sizeof(long) * num_msgs_to_be_merged) );
2551 num_msgs += num_msgs_to_be_merged;
2553 /* Sort the message list, so all the msgid's are in order */
2554 num_msgs = sort_msglist(msglist, num_msgs);
2556 /* Determine the highest message number */
2557 highest_msg = msglist[num_msgs - 1];
2559 /* Write it back to disk. */
2560 cdb_store(CDB_MSGLISTS, &CC->room.QRnumber, (int)sizeof(long),
2561 msglist, (int)(num_msgs * sizeof(long)));
2563 /* Free up the memory we used. */
2566 /* Update the highest-message pointer and unlock the room. */
2567 CC->room.QRhighest = highest_msg;
2568 CtdlPutRoomLock(&CC->room);
2570 /* Perform replication checks if necessary */
2571 if ( (DoesThisRoomNeedEuidIndexing(&CC->room)) && (do_repl_check) ) {
2572 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() doing repl checks\n");
2574 for (i=0; i<num_msgs_to_be_merged; ++i) {
2575 msgid = msgs_to_be_merged[i];
2577 if (supplied_msg != NULL) {
2581 msg = CtdlFetchMessage(msgid, 0);
2585 ReplicationChecks(msg);
2587 /* If the message has an Exclusive ID, index that... */
2588 if (msg->cm_fields['E'] != NULL) {
2589 index_message_by_euid(msg->cm_fields['E'], &CC->room, msgid);
2592 /* Free up the memory we may have allocated */
2593 if (msg != supplied_msg) {
2594 CtdlFreeMessage(msg);
2602 CtdlLogPrintf(CTDL_DEBUG, "CtdlSaveMsgPointerInRoom() skips repl checks\n");
2605 /* Submit this room for processing by hooks */
2606 PerformRoomHooks(&CC->room);
2608 /* Go back to the room we were in before we wandered here... */
2609 CtdlGetRoom(&CC->room, hold_rm);
2611 /* Bump the reference count for all messages which were merged */
2612 if (!suppress_refcount_adj) {
2613 for (i=0; i<num_msgs_to_be_merged; ++i) {
2614 AdjRefCount(msgs_to_be_merged[i], +1);
2618 /* Free up memory... */
2619 if (msgs_to_be_merged != NULL) {
2620 free(msgs_to_be_merged);
2623 /* Return success. */
2629 * This is the same as CtdlSaveMsgPointersInRoom() but it only accepts
2632 int CtdlSaveMsgPointerInRoom(char *roomname, long msgid,
2633 int do_repl_check, struct CtdlMessage *supplied_msg)
2635 return CtdlSaveMsgPointersInRoom(roomname, &msgid, 1, do_repl_check, supplied_msg, 0);
2642 * Message base operation to save a new message to the message store
2643 * (returns new message number)
2645 * This is the back end for CtdlSubmitMsg() and should not be directly
2646 * called by server-side modules.
2649 long send_message(struct CtdlMessage *msg) {
2657 /* Get a new message number */
2658 newmsgid = get_new_message_number();
2659 snprintf(msgidbuf, sizeof msgidbuf, "%010ld@%s", newmsgid, config.c_fqdn);
2661 /* Generate an ID if we don't have one already */
2662 if (msg->cm_fields['I']==NULL) {
2663 msg->cm_fields['I'] = strdup(msgidbuf);
2666 /* If the message is big, set its body aside for storage elsewhere */
2667 if (msg->cm_fields['M'] != NULL) {
2668 if (strlen(msg->cm_fields['M']) > BIGMSG) {
2670 holdM = msg->cm_fields['M'];
2671 msg->cm_fields['M'] = NULL;
2675 /* Serialize our data structure for storage in the database */
2676 serialize_message(&smr, msg);
2679 msg->cm_fields['M'] = holdM;
2683 cprintf("%d Unable to serialize message\n",
2684 ERROR + INTERNAL_ERROR);
2688 /* Write our little bundle of joy into the message base */
2689 if (cdb_store(CDB_MSGMAIN, &newmsgid, (int)sizeof(long),
2690 smr.ser, smr.len) < 0) {
2691 CtdlLogPrintf(CTDL_ERR, "Can't store message\n");
2695 cdb_store(CDB_BIGMSGS,
2705 /* Free the memory we used for the serialized message */
2708 /* Return the *local* message ID to the caller
2709 * (even if we're storing an incoming network message)
2717 * Serialize a struct CtdlMessage into the format used on disk and network.
2719 * This function loads up a "struct ser_ret" (defined in server.h) which
2720 * contains the length of the serialized message and a pointer to the
2721 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2723 void serialize_message(struct ser_ret *ret, /* return values */
2724 struct CtdlMessage *msg) /* unserialized msg */
2726 size_t wlen, fieldlen;
2728 static char *forder = FORDER;
2731 * Check for valid message format
2733 if (is_valid_message(msg) == 0) {
2734 CtdlLogPrintf(CTDL_ERR, "serialize_message() aborting due to invalid message\n");
2741 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL)
2742 ret->len = ret->len +
2743 strlen(msg->cm_fields[(int)forder[i]]) + 2;
2745 ret->ser = malloc(ret->len);
2746 if (ret->ser == NULL) {
2747 CtdlLogPrintf(CTDL_ERR, "serialize_message() malloc(%ld) failed: %s\n",
2748 (long)ret->len, strerror(errno));
2755 ret->ser[1] = msg->cm_anon_type;
2756 ret->ser[2] = msg->cm_format_type;
2759 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2760 fieldlen = strlen(msg->cm_fields[(int)forder[i]]);
2761 ret->ser[wlen++] = (char)forder[i];
2762 safestrncpy((char *)&ret->ser[wlen], msg->cm_fields[(int)forder[i]], fieldlen+1);
2763 wlen = wlen + fieldlen + 1;
2765 if (ret->len != wlen) CtdlLogPrintf(CTDL_ERR, "ERROR: len=%ld wlen=%ld\n",
2766 (long)ret->len, (long)wlen);
2773 * Serialize a struct CtdlMessage into the format used on disk and network.
2775 * This function loads up a "struct ser_ret" (defined in server.h) which
2776 * contains the length of the serialized message and a pointer to the
2777 * serialized message in memory. THE LATTER MUST BE FREED BY THE CALLER.
2779 void dump_message(struct CtdlMessage *msg, /* unserialized msg */
2780 long Siz) /* how many chars ? */
2784 static char *forder = FORDER;
2788 * Check for valid message format
2790 if (is_valid_message(msg) == 0) {
2791 CtdlLogPrintf(CTDL_ERR, "dump_message() aborting due to invalid message\n");
2795 buf = (char*) malloc (Siz + 1);
2799 for (i=0; i<26; ++i) if (msg->cm_fields[(int)forder[i]] != NULL) {
2800 snprintf (buf, Siz, " msg[%c] = %s ...\n", (char) forder[i],
2801 msg->cm_fields[(int)forder[i]]);
2802 client_write (buf, strlen(buf));
2811 * Check to see if any messages already exist in the current room which
2812 * carry the same Exclusive ID as this one. If any are found, delete them.
2814 void ReplicationChecks(struct CtdlMessage *msg) {
2815 long old_msgnum = (-1L);
2817 if (DoesThisRoomNeedEuidIndexing(&CC->room) == 0) return;
2819 CtdlLogPrintf(CTDL_DEBUG, "Performing replication checks in <%s>\n",
2822 /* No exclusive id? Don't do anything. */
2823 if (msg == NULL) return;
2824 if (msg->cm_fields['E'] == NULL) return;
2825 if (IsEmptyStr(msg->cm_fields['E'])) return;
2826 /*CtdlLogPrintf(CTDL_DEBUG, "Exclusive ID: <%s> for room <%s>\n",
2827 msg->cm_fields['E'], CC->room.QRname);*/
2829 old_msgnum = CtdlLocateMessageByEuid(msg->cm_fields['E'], &CC->room);
2830 if (old_msgnum > 0L) {
2831 CtdlLogPrintf(CTDL_DEBUG, "ReplicationChecks() replacing message %ld\n", old_msgnum);
2832 CtdlDeleteMessages(CC->room.QRname, &old_msgnum, 1, "");
2839 * Save a message to disk and submit it into the delivery system.
2841 long CtdlSubmitMsg(struct CtdlMessage *msg, /* message to save */
2842 struct recptypes *recps, /* recipients (if mail) */
2843 char *force, /* force a particular room? */
2844 int flags /* should the message be exported clean? */
2846 char submit_filename[128];
2847 char generated_timestamp[32];
2848 char hold_rm[ROOMNAMELEN];
2849 char actual_rm[ROOMNAMELEN];
2850 char force_room[ROOMNAMELEN];
2851 char content_type[SIZ]; /* We have to learn this */
2852 char recipient[SIZ];
2854 const char *mptr = NULL;
2855 struct ctdluser userbuf;
2857 struct MetaData smi;
2858 FILE *network_fp = NULL;
2859 static int seqnum = 1;
2860 struct CtdlMessage *imsg = NULL;
2862 size_t instr_alloc = 0;
2864 char *hold_R, *hold_D;
2865 char *collected_addresses = NULL;
2866 struct addresses_to_be_filed *aptr = NULL;
2867 StrBuf *saved_rfc822_version = NULL;
2868 int qualified_for_journaling = 0;
2869 CitContext *CCC = CC; /* CachedCitContext - performance boost */
2870 char bounce_to[1024] = "";
2874 CtdlLogPrintf(CTDL_DEBUG, "CtdlSubmitMsg() called\n");
2875 if (is_valid_message(msg) == 0) return(-1); /* self check */
2877 /* If this message has no timestamp, we take the liberty of
2878 * giving it one, right now.
2880 if (msg->cm_fields['T'] == NULL) {
2881 snprintf(generated_timestamp, sizeof generated_timestamp, "%ld", (long)time(NULL));
2882 msg->cm_fields['T'] = strdup(generated_timestamp);
2885 /* If this message has no path, we generate one.
2887 if (msg->cm_fields['P'] == NULL) {
2888 if (msg->cm_fields['A'] != NULL) {
2889 msg->cm_fields['P'] = strdup(msg->cm_fields['A']);
2890 for (a=0; !IsEmptyStr(&msg->cm_fields['P'][a]); ++a) {
2891 if (isspace(msg->cm_fields['P'][a])) {
2892 msg->cm_fields['P'][a] = ' ';
2897 msg->cm_fields['P'] = strdup("unknown");
2901 if (force == NULL) {
2902 strcpy(force_room, "");
2905 strcpy(force_room, force);
2908 /* Learn about what's inside, because it's what's inside that counts */
2909 if (msg->cm_fields['M'] == NULL) {
2910 CtdlLogPrintf(CTDL_ERR, "ERROR: attempt to save message with NULL body\n");
2914 switch (msg->cm_format_type) {
2916 strcpy(content_type, "text/x-citadel-variformat");
2919 strcpy(content_type, "text/plain");
2922 strcpy(content_type, "text/plain");
2923 mptr = bmstrcasestr(msg->cm_fields['M'], "Content-type:");
2926 safestrncpy(content_type, &mptr[13], sizeof content_type);
2927 striplt(content_type);
2928 aptr = content_type;
2929 while (!IsEmptyStr(aptr)) {
2941 /* Goto the correct room */
2942 CtdlLogPrintf(CTDL_DEBUG, "Selected room %s\n", (recps) ? CCC->room.QRname : SENTITEMS);
2943 strcpy(hold_rm, CCC->room.QRname);
2944 strcpy(actual_rm, CCC->room.QRname);
2945 if (recps != NULL) {
2946 strcpy(actual_rm, SENTITEMS);
2949 /* If the user is a twit, move to the twit room for posting */
2951 if (CCC->user.axlevel == AxProbU) {
2952 strcpy(hold_rm, actual_rm);
2953 strcpy(actual_rm, config.c_twitroom);
2954 CtdlLogPrintf(CTDL_DEBUG, "Diverting to twit room\n");
2958 /* ...or if this message is destined for Aide> then go there. */
2959 if (!IsEmptyStr(force_room)) {
2960 strcpy(actual_rm, force_room);
2963 CtdlLogPrintf(CTDL_DEBUG, "Final selection: %s\n", actual_rm);
2964 if (strcasecmp(actual_rm, CCC->room.QRname)) {
2965 /* CtdlGetRoom(&CCC->room, actual_rm); */
2966 CtdlUserGoto(actual_rm, 0, 1, NULL, NULL);
2970 * If this message has no O (room) field, generate one.
2972 if (msg->cm_fields['O'] == NULL) {
2973 msg->cm_fields['O'] = strdup(CCC->room.QRname);
2976 /* Perform "before save" hooks (aborting if any return nonzero) */
2977 CtdlLogPrintf(CTDL_DEBUG, "Performing before-save hooks\n");
2978 if (PerformMessageHooks(msg, EVT_BEFORESAVE) > 0) return(-3);
2981 * If this message has an Exclusive ID, and the room is replication
2982 * checking enabled, then do replication checks.
2984 if (DoesThisRoomNeedEuidIndexing(&CCC->room)) {
2985 ReplicationChecks(msg);
2988 /* Save it to disk */
2989 CtdlLogPrintf(CTDL_DEBUG, "Saving to disk\n");
2990 newmsgid = send_message(msg);
2991 if (newmsgid <= 0L) return(-5);
2993 /* Write a supplemental message info record. This doesn't have to
2994 * be a critical section because nobody else knows about this message
2997 CtdlLogPrintf(CTDL_DEBUG, "Creating MetaData record\n");
2998 memset(&smi, 0, sizeof(struct MetaData));
2999 smi.meta_msgnum = newmsgid;
3000 smi.meta_refcount = 0;
3001 safestrncpy(smi.meta_content_type, content_type,
3002 sizeof smi.meta_content_type);
3005 * Measure how big this message will be when rendered as RFC822.
3006 * We do this for two reasons:
3007 * 1. We need the RFC822 length for the new metadata record, so the
3008 * POP and IMAP services don't have to calculate message lengths
3009 * while the user is waiting (multiplied by potentially hundreds
3010 * or thousands of messages).
3011 * 2. If journaling is enabled, we will need an RFC822 version of the
3012 * message to attach to the journalized copy.
3014 if (CCC->redirect_buffer != NULL) {
3015 CtdlLogPrintf(CTDL_ALERT, "CCC->redirect_buffer is not NULL during message submission!\n");
3018 CCC->redirect_buffer = NewStrBufPlain(NULL, SIZ);
3019 CtdlOutputPreLoadedMsg(msg, MT_RFC822, HEADERS_ALL, 0, 1, QP_EADDR);
3020 smi.meta_rfc822_length = StrLength(CCC->redirect_buffer);
3021 saved_rfc822_version = CCC->redirect_buffer;
3022 CCC->redirect_buffer = NULL;
3026 /* Now figure out where to store the pointers */
3027 CtdlLogPrintf(CTDL_DEBUG, "Storing pointers\n");
3029 /* If this is being done by the networker delivering a private
3030 * message, we want to BYPASS saving the sender's copy (because there
3031 * is no local sender; it would otherwise go to the Trashcan).
3033 if ((!CCC->internal_pgm) || (recps == NULL)) {
3034 if (CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 1, msg) != 0) {
3035 CtdlLogPrintf(CTDL_ERR, "ERROR saving message pointer!\n");
3036 CtdlSaveMsgPointerInRoom(config.c_aideroom, newmsgid, 0, msg);
3040 /* For internet mail, drop a copy in the outbound queue room */
3041 if ((recps != NULL) && (recps->num_internet > 0)) {
3042 CtdlSaveMsgPointerInRoom(SMTP_SPOOLOUT_ROOM, newmsgid, 0, msg);
3045 /* If other rooms are specified, drop them there too. */
3046 if ((recps != NULL) && (recps->num_room > 0))
3047 for (i=0; i<num_tokens(recps->recp_room, '|'); ++i) {
3048 extract_token(recipient, recps->recp_room, i,
3049 '|', sizeof recipient);
3050 CtdlLogPrintf(CTDL_DEBUG, "Delivering to room <%s>\n", recipient);
3051 CtdlSaveMsgPointerInRoom(recipient, newmsgid, 0, msg);
3054 /* Bump this user's messages posted counter. */
3055 CtdlLogPrintf(CTDL_DEBUG, "Updating user\n");
3056 CtdlGetUserLock(&CCC->user, CCC->curr_user);
3057 CCC->user.posted = CCC->user.posted + 1;
3058 CtdlPutUserLock(&CCC->user);
3060 /* Decide where bounces need to be delivered */
3061 if ((recps != NULL) && (recps->bounce_to != NULL)) {
3062 safestrncpy(bounce_to, recps->bounce_to, sizeof bounce_to);
3064 else if (CCC->logged_in) {
3065 snprintf(bounce_to, sizeof bounce_to, "%s@%s", CCC->user.fullname, config.c_nodename);
3068 snprintf(bounce_to, sizeof bounce_to, "%s@%s", msg->cm_fields['A'], msg->cm_fields['N']);
3071 /* If this is private, local mail, make a copy in the
3072 * recipient's mailbox and bump the reference count.
3074 if ((recps != NULL) && (recps->num_local > 0))
3075 for (i=0; i<num_tokens(recps->recp_local, '|'); ++i) {
3076 extract_token(recipient, recps->recp_local, i,
3077 '|', sizeof recipient);
3078 CtdlLogPrintf(CTDL_DEBUG, "Delivering private local mail to <%s>\n",
3080 if (CtdlGetUser(&userbuf, recipient) == 0) {
3081 // Add a flag so the Funambol module knows its mail
3082 msg->cm_fields['W'] = strdup(recipient);
3083 CtdlMailboxName(actual_rm, sizeof actual_rm, &userbuf, MAILROOM);
3084 CtdlSaveMsgPointerInRoom(actual_rm, newmsgid, 0, msg);
3085 CtdlBumpNewMailCounter(userbuf.usernum);
3086 if (!IsEmptyStr(config.c_funambol_host) || !IsEmptyStr(config.c_pager_program)) {
3087 /* Generate a instruction message for the Funambol notification
3088 * server, in the same style as the SMTP queue
3091 instr = malloc(instr_alloc);
3092 snprintf(instr, instr_alloc,
3093 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3095 SPOOLMIME, newmsgid, (long)time(NULL),
3099 imsg = malloc(sizeof(struct CtdlMessage));
3100 memset(imsg, 0, sizeof(struct CtdlMessage));
3101 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3102 imsg->cm_anon_type = MES_NORMAL;
3103 imsg->cm_format_type = FMT_RFC822;
3104 imsg->cm_fields['A'] = strdup("Citadel");
3105 imsg->cm_fields['J'] = strdup("do not journal");
3106 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3107 imsg->cm_fields['W'] = strdup(recipient);
3108 CtdlSubmitMsg(imsg, NULL, FNBL_QUEUE_ROOM, 0);
3109 CtdlFreeMessage(imsg);
3113 CtdlLogPrintf(CTDL_DEBUG, "No user <%s>\n", recipient);
3114 CtdlSaveMsgPointerInRoom(config.c_aideroom,
3119 /* Perform "after save" hooks */
3120 CtdlLogPrintf(CTDL_DEBUG, "Performing after-save hooks\n");
3121 PerformMessageHooks(msg, EVT_AFTERSAVE);
3123 /* For IGnet mail, we have to save a new copy into the spooler for
3124 * each recipient, with the R and D fields set to the recipient and
3125 * destination-node. This has two ugly side effects: all other
3126 * recipients end up being unlisted in this recipient's copy of the
3127 * message, and it has to deliver multiple messages to the same
3128 * node. We'll revisit this again in a year or so when everyone has
3129 * a network spool receiver that can handle the new style messages.
3131 if ((recps != NULL) && (recps->num_ignet > 0))
3132 for (i=0; i<num_tokens(recps->recp_ignet, '|'); ++i) {
3133 extract_token(recipient, recps->recp_ignet, i,
3134 '|', sizeof recipient);
3136 hold_R = msg->cm_fields['R'];
3137 hold_D = msg->cm_fields['D'];
3138 msg->cm_fields['R'] = malloc(SIZ);
3139 msg->cm_fields['D'] = malloc(128);
3140 extract_token(msg->cm_fields['R'], recipient, 0, '@', SIZ);
3141 extract_token(msg->cm_fields['D'], recipient, 1, '@', 128);
3143 serialize_message(&smr, msg);
3145 snprintf(submit_filename, sizeof submit_filename,
3146 "%s/netmail.%04lx.%04x.%04x",
3148 (long) getpid(), CCC->cs_pid, ++seqnum);
3149 network_fp = fopen(submit_filename, "wb+");
3150 if (network_fp != NULL) {
3151 rv = fwrite(smr.ser, smr.len, 1, network_fp);
3157 free(msg->cm_fields['R']);
3158 free(msg->cm_fields['D']);
3159 msg->cm_fields['R'] = hold_R;
3160 msg->cm_fields['D'] = hold_D;
3163 /* Go back to the room we started from */
3164 CtdlLogPrintf(CTDL_DEBUG, "Returning to original room %s\n", hold_rm);
3165 if (strcasecmp(hold_rm, CCC->room.QRname))
3166 CtdlUserGoto(hold_rm, 0, 1, NULL, NULL);
3168 /* For internet mail, generate delivery instructions.
3169 * Yes, this is recursive. Deal with it. Infinite recursion does
3170 * not happen because the delivery instructions message does not
3171 * contain a recipient.
3173 if ((recps != NULL) && (recps->num_internet > 0)) {
3174 CtdlLogPrintf(CTDL_DEBUG, "Generating delivery instructions\n");
3176 instr = malloc(instr_alloc);
3177 snprintf(instr, instr_alloc,
3178 "Content-type: %s\n\nmsgid|%ld\nsubmitted|%ld\n"
3180 SPOOLMIME, newmsgid, (long)time(NULL),
3184 if (recps->envelope_from != NULL) {
3185 tmp = strlen(instr);
3186 snprintf(&instr[tmp], instr_alloc-tmp, "envelope_from|%s\n", recps->envelope_from);
3189 for (i=0; i<num_tokens(recps->recp_internet, '|'); ++i) {
3190 tmp = strlen(instr);
3191 extract_token(recipient, recps->recp_internet, i, '|', sizeof recipient);
3192 if ((tmp + strlen(recipient) + 32) > instr_alloc) {
3193 instr_alloc = instr_alloc * 2;
3194 instr = realloc(instr, instr_alloc);
3196 snprintf(&instr[tmp], instr_alloc - tmp, "remote|%s|0||\n", recipient);
3199 imsg = malloc(sizeof(struct CtdlMessage));
3200 memset(imsg, 0, sizeof(struct CtdlMessage));
3201 imsg->cm_magic = CTDLMESSAGE_MAGIC;
3202 imsg->cm_anon_type = MES_NORMAL;
3203 imsg->cm_format_type = FMT_RFC822;
3204 imsg->cm_fields['A'] = strdup("Citadel");
3205 imsg->cm_fields['J'] = strdup("do not journal");
3206 imsg->cm_fields['M'] = instr; /* imsg owns this memory now */
3207 CtdlSubmitMsg(imsg, NULL, SMTP_SPOOLOUT_ROOM, QP_EADDR);
3208 CtdlFreeMessage(imsg);
3212 * Any addresses to harvest for someone's address book?
3214 if ( (CCC->logged_in) && (recps != NULL) ) {
3215 collected_addresses = harvest_collected_addresses(msg);
3218 if (collected_addresses != NULL) {
3219 aptr = (struct addresses_to_be_filed *)
3220 malloc(sizeof(struct addresses_to_be_filed));
3221 CtdlMailboxName(actual_rm, sizeof actual_rm,
3222 &CCC->user, USERCONTACTSROOM);
3223 aptr->roomname = strdup(actual_rm);
3224 aptr->collected_addresses = collected_addresses;
3225 begin_critical_section(S_ATBF);
3228 end_critical_section(S_ATBF);
3232 * Determine whether this message qualifies for journaling.
3234 if (msg->cm_fields['J'] != NULL) {
3235 qualified_for_journaling = 0;
3238 if (recps == NULL) {
3239 qualified_for_journaling = config.c_journal_pubmsgs;
3241 else if (recps->num_local + recps->num_ignet + recps->num_internet > 0) {
3242 qualified_for_journaling = config.c_journal_email;
3245 qualified_for_journaling = config.c_journal_pubmsgs;
3250 * Do we have to perform journaling? If so, hand off the saved
3251 * RFC822 version will be handed off to the journaler for background
3252 * submit. Otherwise, we have to free the memory ourselves.
3254 if (saved_rfc822_version != NULL) {
3255 if (qualified_for_journaling) {
3256 JournalBackgroundSubmit(msg, saved_rfc822_version, recps);
3259 FreeStrBuf(&saved_rfc822_version);
3269 void aide_message (char *text, char *subject)
3271 quickie_message("Citadel",NULL,NULL,AIDEROOM,text,FMT_CITADEL,subject);
3276 * Convenience function for generating small administrative messages.
3278 void quickie_message(const char *from, const char *fromaddr, char *to, char *room, const char *text,
3279 int format_type, const char *subject)
3281 struct CtdlMessage *msg;
3282 struct recptypes *recp = NULL;
3284 msg = malloc(sizeof(struct CtdlMessage));
3285 memset(msg, 0, sizeof(struct CtdlMessage));
3286 msg->cm_magic = CTDLMESSAGE_MAGIC;
3287 msg->cm_anon_type = MES_NORMAL;
3288 msg->cm_format_type = format_type;
3291 msg->cm_fields['A'] = strdup(from);
3293 else if (fromaddr != NULL) {
3294 msg->cm_fields['A'] = strdup(fromaddr);
3295 if (strchr(msg->cm_fields['A'], '@')) {
3296 *strchr(msg->cm_fields['A'], '@') = 0;
3300 msg->cm_fields['A'] = strdup("Citadel");
3303 if (fromaddr != NULL) msg->cm_fields['F'] = strdup(fromaddr);
3304 if (room != NULL) msg->cm_fields['O'] = strdup(room);
3305 msg->cm_fields['N'] = strdup(NODENAME);
3307 msg->cm_fields['R'] = strdup(to);
3308 recp = validate_recipients(to, NULL, 0);
3310 if (subject != NULL) {
3311 msg->cm_fields['U'] = strdup(subject);
3313 msg->cm_fields['M'] = strdup(text);
3315 CtdlSubmitMsg(msg, recp, room, 0);
3316 CtdlFreeMessage(msg);
3317 if (recp != NULL) free_recipients(recp);
3323 * Back end function used by CtdlMakeMessage() and similar functions
3325 StrBuf *CtdlReadMessageBodyBuf(char *terminator, /* token signalling EOT */
3327 size_t maxlen, /* maximum message length */
3328 char *exist, /* if non-null, append to it;
3329 exist is ALWAYS freed */
3330 int crlf, /* CRLF newlines instead of LF */
3331 int *sock /* socket handle or 0 for this session's client socket */
3340 LineBuf = NewStrBufPlain(NULL, SIZ);
3341 if (exist == NULL) {
3342 Message = NewStrBufPlain(NULL, 4 * SIZ);
3345 Message = NewStrBufPlain(exist, -1);
3349 /* Do we need to change leading ".." to "." for SMTP escaping? */
3350 if ((tlen == 1) && (*terminator == '.')) {
3354 /* read in the lines of message text one by one */
3357 if ((CtdlSockGetLine(sock, LineBuf, 5) < 0) ||
3362 if (CtdlClientGetLine(LineBuf) < 0) finished = 1;
3364 if ((StrLength(LineBuf) == tlen) &&
3365 (!strcmp(ChrPtr(LineBuf), terminator)))
3368 if ( (!flushing) && (!finished) ) {
3370 StrBufAppendBufPlain(LineBuf, HKEY("\r\n"), 0);
3373 StrBufAppendBufPlain(LineBuf, HKEY("\n"), 0);
3376 /* Unescape SMTP-style input of two dots at the beginning of the line */
3378 (StrLength(LineBuf) == 2) &&
3379 (!strcmp(ChrPtr(LineBuf), "..")))
3381 StrBufCutLeft(LineBuf, 1);
3384 StrBufAppendBuf(Message, LineBuf, 0);
3387 /* if we've hit the max msg length, flush the rest */
3388 if (StrLength(Message) >= maxlen) flushing = 1;
3390 } while (!finished);
3391 FreeStrBuf(&LineBuf);
3397 * Back end function used by CtdlMakeMessage() and similar functions
3399 char *CtdlReadMessageBody(char *terminator, /* token signalling EOT */
3401 size_t maxlen, /* maximum message length */
3402 char *exist, /* if non-null, append to it;
3403 exist is ALWAYS freed */
3404 int crlf, /* CRLF newlines instead of LF */
3405 int *sock /* socket handle or 0 for this session's client socket */
3410 Message = CtdlReadMessageBodyBuf(terminator,
3416 if (Message == NULL)
3419 return SmashStrBuf(&Message);
3424 * Build a binary message to be saved on disk.
3425 * (NOTE: if you supply 'preformatted_text', the buffer you give it
3426 * will become part of the message. This means you are no longer
3427 * responsible for managing that memory -- it will be freed along with
3428 * the rest of the fields when CtdlFreeMessage() is called.)
3431 struct CtdlMessage *CtdlMakeMessage(
3432 struct ctdluser *author, /* author's user structure */
3433 char *recipient, /* NULL if it's not mail */
3434 char *recp_cc, /* NULL if it's not mail */
3435 char *room, /* room where it's going */
3436 int type, /* see MES_ types in header file */
3437 int format_type, /* variformat, plain text, MIME... */
3438 char *fake_name, /* who we're masquerading as */
3439 char *my_email, /* which of my email addresses to use (empty is ok) */
3440 char *subject, /* Subject (optional) */
3441 char *supplied_euid, /* ...or NULL if this is irrelevant */
3442 char *preformatted_text, /* ...or NULL to read text from client */
3443 char *references /* Thread references */
3445 char dest_node[256];
3447 struct CtdlMessage *msg;
3449 StrBuf *FakeEncAuthor = NULL;
3451 msg = malloc(sizeof(struct CtdlMessage));
3452 memset(msg, 0, sizeof(struct CtdlMessage));
3453 msg->cm_magic = CTDLMESSAGE_MAGIC;
3454 msg->cm_anon_type = type;
3455 msg->cm_format_type = format_type;
3457 /* Don't confuse the poor folks if it's not routed mail. */
3458 strcpy(dest_node, "");
3460 if (recipient != NULL) striplt(recipient);
3461 if (recp_cc != NULL) striplt(recp_cc);
3463 /* Path or Return-Path */
3464 if (my_email == NULL) my_email = "";
3466 if (!IsEmptyStr(my_email)) {
3467 msg->cm_fields['P'] = strdup(my_email);
3470 snprintf(buf, sizeof buf, "%s", author->fullname);
3471 msg->cm_fields['P'] = strdup(buf);
3473 convert_spaces_to_underscores(msg->cm_fields['P']);
3475 snprintf(buf, sizeof buf, "%ld", (long)time(NULL)); /* timestamp */
3476 msg->cm_fields['T'] = strdup(buf);
3478 if ((fake_name != NULL) && (fake_name[0])) { /* author */
3479 FakeAuthor = NewStrBufPlain (fake_name, -1);
3482 FakeAuthor = NewStrBufPlain (author->fullname, -1);
3484 StrBufRFC2047encode(&FakeEncAuthor, FakeAuthor);
3485 msg->cm_fields['A'] = SmashStrBuf(&FakeEncAuthor);
3486 FreeStrBuf(&FakeAuthor);
3488 if (CC->room.QRflags & QR_MAILBOX) { /* room */
3489 msg->cm_fields['O'] = strdup(&CC->room.QRname[11]);
3492 msg->cm_fields['O'] = strdup(CC->room.QRname);
3495 msg->cm_fields['N'] = strdup(NODENAME); /* nodename */
3496 msg->cm_fields['H'] = strdup(HUMANNODE); /* hnodename */
3498 if ((recipient != NULL) && (recipient[0] != 0)) {
3499 msg->cm_fields['R'] = strdup(recipient);
3501 if ((recp_cc != NULL) && (recp_cc[0] != 0)) {
3502 msg->cm_fields['Y'] = strdup(recp_cc);
3504 if (dest_node[0] != 0) {
3505 msg->cm_fields['D'] = strdup(dest_node);
3508 if (!IsEmptyStr(my_email)) {
3509 msg->cm_fields['F'] = strdup(my_email);
3511 else if ( (author == &CC->user) && (!IsEmptyStr(CC->cs_inet_email)) ) {
3512 msg->cm_fields['F'] = strdup(CC->cs_inet_email);
3515 if (subject != NULL) {
3518 length = strlen(subject);
3524 while ((subject[i] != '\0') &&
3525 (IsAscii = isascii(subject[i]) != 0 ))
3528 msg->cm_fields['U'] = strdup(subject);
3529 else /* ok, we've got utf8 in the string. */
3531 msg->cm_fields['U'] = rfc2047encode(subject, length);
3537 if (supplied_euid != NULL) {
3538 msg->cm_fields['E'] = strdup(supplied_euid);
3541 if (references != NULL) {
3542 if (!IsEmptyStr(references)) {
3543 msg->cm_fields['W'] = strdup(references);
3547 if (preformatted_text != NULL) {
3548 msg->cm_fields['M'] = preformatted_text;
3551 msg->cm_fields['M'] = CtdlReadMessageBody(HKEY("000"), config.c_maxmsglen, NULL, 0, 0);
3559 * Check to see whether we have permission to post a message in the current
3560 * room. Returns a *CITADEL ERROR CODE* and puts a message in errmsgbuf, or
3561 * returns 0 on success.
3563 int CtdlDoIHavePermissionToPostInThisRoom(char *errmsgbuf,
3565 const char* RemoteIdentifier,
3569 if (!(CC->logged_in) &&
3570 (PostPublic == POST_LOGGED_IN)) {
3571 snprintf(errmsgbuf, n, "Not logged in.");
3572 return (ERROR + NOT_LOGGED_IN);
3574 else if (PostPublic == CHECK_EXISTANCE) {
3575 return (0); // We're Evaling whether a recipient exists
3577 else if (!(CC->logged_in)) {
3579 if ((CC->room.QRflags & QR_READONLY)) {
3580 snprintf(errmsgbuf, n, "Not logged in.");
3581 return (ERROR + NOT_LOGGED_IN);
3583 if (CC->room.QRflags2 & QR2_MODERATED) {
3584 snprintf(errmsgbuf, n, "Not logged in Moderation feature not yet implemented!");
3585 return (ERROR + NOT_LOGGED_IN);
3587 if ((PostPublic!=POST_LMTP) &&(CC->room.QRflags2 & QR2_SMTP_PUBLIC) == 0) {
3592 if (RemoteIdentifier == NULL)
3594 snprintf(errmsgbuf, n, "Need sender to permit access.");
3595 return (ERROR + USERNAME_REQUIRED);
3598 assoc_file_name(filename, sizeof filename, &CC->room, ctdl_netcfg_dir);
3599 begin_critical_section(S_NETCONFIGS);
3600 if (!read_spoolcontrol_file(&sc, filename))
3602 end_critical_section(S_NETCONFIGS);
3603 snprintf(errmsgbuf, n,
3604 "This mailing list only accepts posts from subscribers.");
3605 return (ERROR + NO_SUCH_USER);
3607 end_critical_section(S_NETCONFIGS);
3608 found = is_recipient (sc, RemoteIdentifier);
3609 free_spoolcontrol_struct(&sc);
3614 snprintf(errmsgbuf, n,
3615 "This mailing list only accepts posts from subscribers.");
3616 return (ERROR + NO_SUCH_USER);
3623 if ((CC->user.axlevel < AxProbU)
3624 && ((CC->room.QRflags & QR_MAILBOX) == 0)) {
3625 snprintf(errmsgbuf, n, "Need to be validated to enter "
3626 "(except in %s> to sysop)", MAILROOM);
3627 return (ERROR + HIGHER_ACCESS_REQUIRED);
3630 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
3631 if (!(ra & UA_POSTALLOWED)) {
3632 snprintf(errmsgbuf, n, "Higher access is required to post in this room.");
3633 return (ERROR + HIGHER_ACCESS_REQUIRED);
3636 strcpy(errmsgbuf, "Ok");
3642 * Check to see if the specified user has Internet mail permission
3643 * (returns nonzero if permission is granted)
3645 int CtdlCheckInternetMailPermission(struct ctdluser *who) {
3647 /* Do not allow twits to send Internet mail */
3648 if (who->axlevel <= AxProbU) return(0);
3650 /* Globally enabled? */
3651 if (config.c_restrict == 0) return(1);
3653 /* User flagged ok? */
3654 if (who->flags & US_INTERNET) return(2);
3656 /* Aide level access? */
3657 if (who->axlevel >= AxAideU) return(3);
3659 /* No mail for you! */
3665 * Validate recipients, count delivery types and errors, and handle aliasing
3666 * FIXME check for dupes!!!!!
3668 * Returns 0 if all addresses are ok, ret->num_error = -1 if no addresses
3669 * were specified, or the number of addresses found invalid.
3671 * Caller needs to free the result using free_recipients()
3673 struct recptypes *validate_recipients(const char *supplied_recipients,
3674 const char *RemoteIdentifier,
3676 struct recptypes *ret;
3677 char *recipients = NULL;
3678 char this_recp[256];
3679 char this_recp_cooked[256];
3685 struct ctdluser tempUS;
3686 struct ctdlroom tempQR;
3687 struct ctdlroom tempQR2;
3693 ret = (struct recptypes *) malloc(sizeof(struct recptypes));
3694 if (ret == NULL) return(NULL);
3696 /* Set all strings to null and numeric values to zero */
3697 memset(ret, 0, sizeof(struct recptypes));
3699 if (supplied_recipients == NULL) {
3700 recipients = strdup("");
3703 recipients = strdup(supplied_recipients);
3706 /* Allocate some memory. Yes, this allocates 500% more memory than we will
3707 * actually need, but it's healthier for the heap than doing lots of tiny
3708 * realloc() calls instead.
3711 ret->errormsg = malloc(strlen(recipients) + 1024);
3712 ret->recp_local = malloc(strlen(recipients) + 1024);
3713 ret->recp_internet = malloc(strlen(recipients) + 1024);
3714 ret->recp_ignet = malloc(strlen(recipients) + 1024);
3715 ret->recp_room = malloc(strlen(recipients) + 1024);
3716 ret->display_recp = malloc(strlen(recipients) + 1024);
3718 ret->errormsg[0] = 0;
3719 ret->recp_local[0] = 0;
3720 ret->recp_internet[0] = 0;
3721 ret->recp_ignet[0] = 0;
3722 ret->recp_room[0] = 0;
3723 ret->display_recp[0] = 0;
3725 ret->recptypes_magic = RECPTYPES_MAGIC;
3727 /* Change all valid separator characters to commas */
3728 for (i=0; !IsEmptyStr(&recipients[i]); ++i) {
3729 if ((recipients[i] == ';') || (recipients[i] == '|')) {
3730 recipients[i] = ',';
3734 /* Now start extracting recipients... */
3736 while (!IsEmptyStr(recipients)) {
3738 for (i=0; i<=strlen(recipients); ++i) {
3739 if (recipients[i] == '\"') in_quotes = 1 - in_quotes;
3740 if ( ( (recipients[i] == ',') && (!in_quotes) ) || (recipients[i] == 0) ) {
3741 safestrncpy(this_recp, recipients, i+1);
3743 if (recipients[i] == ',') {
3744 strcpy(recipients, &recipients[i+1]);
3747 strcpy(recipients, "");
3754 if (IsEmptyStr(this_recp))
3756 CtdlLogPrintf(CTDL_DEBUG, "Evaluating recipient #%d: %s\n", num_recps, this_recp);
3758 mailtype = alias(this_recp);
3759 mailtype = alias(this_recp);
3760 mailtype = alias(this_recp);
3762 for (j=0; !IsEmptyStr(&this_recp[j]); ++j) {
3763 if (this_recp[j]=='_') {
3764 this_recp_cooked[j] = ' ';
3767 this_recp_cooked[j] = this_recp[j];
3770 this_recp_cooked[j] = '\0';
3775 if (!strcasecmp(this_recp, "sysop")) {
3777 strcpy(this_recp, config.c_aideroom);
3778 if (!IsEmptyStr(ret->recp_room)) {
3779 strcat(ret->recp_room, "|");
3781 strcat(ret->recp_room, this_recp);
3783 else if ( (!strncasecmp(this_recp, "room_", 5))
3784 && (!CtdlGetRoom(&tempQR, &this_recp_cooked[5])) ) {
3786 /* Save room so we can restore it later */
3790 /* Check permissions to send mail to this room */
3791 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg,
3803 if (!IsEmptyStr(ret->recp_room)) {
3804 strcat(ret->recp_room, "|");
3806 strcat(ret->recp_room, &this_recp_cooked[5]);
3809 /* Restore room in case something needs it */
3813 else if (CtdlGetUser(&tempUS, this_recp) == 0) {
3815 strcpy(this_recp, tempUS.fullname);
3816 if (!IsEmptyStr(ret->recp_local)) {
3817 strcat(ret->recp_local, "|");
3819 strcat(ret->recp_local, this_recp);
3821 else if (CtdlGetUser(&tempUS, this_recp_cooked) == 0) {
3823 strcpy(this_recp, tempUS.fullname);
3824 if (!IsEmptyStr(ret->recp_local)) {
3825 strcat(ret->recp_local, "|");
3827 strcat(ret->recp_local, this_recp);
3835 /* Yes, you're reading this correctly: if the target
3836 * domain points back to the local system or an attached
3837 * Citadel directory, the address is invalid. That's
3838 * because if the address were valid, we would have
3839 * already translated it to a local address by now.
3841 if (IsDirectory(this_recp, 0)) {
3846 ++ret->num_internet;
3847 if (!IsEmptyStr(ret->recp_internet)) {
3848 strcat(ret->recp_internet, "|");
3850 strcat(ret->recp_internet, this_recp);
3855 if (!IsEmptyStr(ret->recp_ignet)) {
3856 strcat(ret->recp_ignet, "|");
3858 strcat(ret->recp_ignet, this_recp);
3866 if (IsEmptyStr(errmsg)) {
3867 snprintf(append, sizeof append, "Invalid recipient: %s", this_recp);
3870 snprintf(append, sizeof append, "%s", errmsg);
3872 if ( (strlen(ret->errormsg) + strlen(append) + 3) < SIZ) {
3873 if (!IsEmptyStr(ret->errormsg)) {
3874 strcat(ret->errormsg, "; ");
3876 strcat(ret->errormsg, append);
3880 if (IsEmptyStr(ret->display_recp)) {
3881 strcpy(append, this_recp);
3884 snprintf(append, sizeof append, ", %s", this_recp);
3886 if ( (strlen(ret->display_recp)+strlen(append)) < SIZ) {
3887 strcat(ret->display_recp, append);
3892 if ((ret->num_local + ret->num_internet + ret->num_ignet +
3893 ret->num_room + ret->num_error) == 0) {
3894 ret->num_error = (-1);
3895 strcpy(ret->errormsg, "No recipients specified.");
3898 CtdlLogPrintf(CTDL_DEBUG, "validate_recipients()\n");
3899 CtdlLogPrintf(CTDL_DEBUG, " local: %d <%s>\n", ret->num_local, ret->recp_local);
3900 CtdlLogPrintf(CTDL_DEBUG, " room: %d <%s>\n", ret->num_room, ret->recp_room);
3901 CtdlLogPrintf(CTDL_DEBUG, " inet: %d <%s>\n", ret->num_internet, ret->recp_internet);
3902 CtdlLogPrintf(CTDL_DEBUG, " ignet: %d <%s>\n", ret->num_ignet, ret->recp_ignet);
3903 CtdlLogPrintf(CTDL_DEBUG, " error: %d <%s>\n", ret->num_error, ret->errormsg);
3911 * Destructor for struct recptypes
3913 void free_recipients(struct recptypes *valid) {
3915 if (valid == NULL) {
3919 if (valid->recptypes_magic != RECPTYPES_MAGIC) {
3920 CtdlLogPrintf(CTDL_EMERG, "Attempt to call free_recipients() on some other data type!\n");
3924 if (valid->errormsg != NULL) free(valid->errormsg);
3925 if (valid->recp_local != NULL) free(valid->recp_local);
3926 if (valid->recp_internet != NULL) free(valid->recp_internet);
3927 if (valid->recp_ignet != NULL) free(valid->recp_ignet);
3928 if (valid->recp_room != NULL) free(valid->recp_room);
3929 if (valid->display_recp != NULL) free(valid->display_recp);
3930 if (valid->bounce_to != NULL) free(valid->bounce_to);
3931 if (valid->envelope_from != NULL) free(valid->envelope_from);
3938 * message entry - mode 0 (normal)
3940 void cmd_ent0(char *entargs)
3946 char supplied_euid[128];
3948 int format_type = 0;
3949 char newusername[256];
3950 char newuseremail[256];
3951 struct CtdlMessage *msg;
3955 struct recptypes *valid = NULL;
3956 struct recptypes *valid_to = NULL;
3957 struct recptypes *valid_cc = NULL;
3958 struct recptypes *valid_bcc = NULL;
3960 int subject_required = 0;
3965 int newuseremail_ok = 0;
3966 char references[SIZ];
3971 post = extract_int(entargs, 0);
3972 extract_token(recp, entargs, 1, '|', sizeof recp);
3973 anon_flag = extract_int(entargs, 2);
3974 format_type = extract_int(entargs, 3);
3975 extract_token(subject, entargs, 4, '|', sizeof subject);
3976 extract_token(newusername, entargs, 5, '|', sizeof newusername);
3977 do_confirm = extract_int(entargs, 6);
3978 extract_token(cc, entargs, 7, '|', sizeof cc);
3979 extract_token(bcc, entargs, 8, '|', sizeof bcc);
3980 switch(CC->room.QRdefaultview) {
3983 extract_token(supplied_euid, entargs, 9, '|', sizeof supplied_euid);
3986 supplied_euid[0] = 0;
3989 extract_token(newuseremail, entargs, 10, '|', sizeof newuseremail);
3990 extract_token(references, entargs, 11, '|', sizeof references);
3991 for (ptr=references; *ptr != 0; ++ptr) {
3992 if (*ptr == '!') *ptr = '|';
3995 /* first check to make sure the request is valid. */
3997 err = CtdlDoIHavePermissionToPostInThisRoom(errmsg, sizeof errmsg, NULL, POST_LOGGED_IN);
4000 cprintf("%d %s\n", err, errmsg);
4004 /* Check some other permission type things. */
4006 if (IsEmptyStr(newusername)) {
4007 strcpy(newusername, CC->user.fullname);
4009 if ( (CC->user.axlevel < AxAideU)
4010 && (strcasecmp(newusername, CC->user.fullname))
4011 && (strcasecmp(newusername, CC->cs_inet_fn))
4013 cprintf("%d You don't have permission to author messages as '%s'.\n",
4014 ERROR + HIGHER_ACCESS_REQUIRED,
4021 if (IsEmptyStr(newuseremail)) {
4022 newuseremail_ok = 1;
4025 if (!IsEmptyStr(newuseremail)) {
4026 if (!strcasecmp(newuseremail, CC->cs_inet_email)) {
4027 newuseremail_ok = 1;
4029 else if (!IsEmptyStr(CC->cs_inet_other_emails)) {
4030 j = num_tokens(CC->cs_inet_other_emails, '|');
4031 for (i=0; i<j; ++i) {
4032 extract_token(buf, CC->cs_inet_other_emails, i, '|', sizeof buf);
4033 if (!strcasecmp(newuseremail, buf)) {
4034 newuseremail_ok = 1;
4040 if (!newuseremail_ok) {
4041 cprintf("%d You don't have permission to author messages as '%s'.\n",
4042 ERROR + HIGHER_ACCESS_REQUIRED,
4048 CC->cs_flags |= CS_POSTING;
4050 /* In mailbox rooms we have to behave a little differently --
4051 * make sure the user has specified at least one recipient. Then
4052 * validate the recipient(s). We do this for the Mail> room, as
4053 * well as any room which has the "Mailbox" view set.
4056 if ( ( (CC->room.QRflags & QR_MAILBOX) && (!strcasecmp(&CC->room.QRname[11], MAILROOM)) )
4057 || ( (CC->room.QRflags & QR_MAILBOX) && (CC->curr_view == VIEW_MAILBOX) )
4059 if (CC->user.axlevel < AxProbU) {
4060 strcpy(recp, "sysop");
4065 valid_to = validate_recipients(recp, NULL, 0);
4066 if (valid_to->num_error > 0) {
4067 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_to->errormsg);
4068 free_recipients(valid_to);
4072 valid_cc = validate_recipients(cc, NULL, 0);
4073 if (valid_cc->num_error > 0) {
4074 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_cc->errormsg);
4075 free_recipients(valid_to);
4076 free_recipients(valid_cc);
4080 valid_bcc = validate_recipients(bcc, NULL, 0);
4081 if (valid_bcc->num_error > 0) {
4082 cprintf("%d %s\n", ERROR + NO_SUCH_USER, valid_bcc->errormsg);
4083 free_recipients(valid_to);
4084 free_recipients(valid_cc);
4085 free_recipients(valid_bcc);
4089 /* Recipient required, but none were specified */
4090 if ( (valid_to->num_error < 0) && (valid_cc->num_error < 0) && (valid_bcc->num_error < 0) ) {
4091 free_recipients(valid_to);
4092 free_recipients(valid_cc);
4093 free_recipients(valid_bcc);
4094 cprintf("%d At least one recipient is required.\n", ERROR + NO_SUCH_USER);
4098 if (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0) {
4099 if (CtdlCheckInternetMailPermission(&CC->user)==0) {
4100 cprintf("%d You do not have permission "
4101 "to send Internet mail.\n",
4102 ERROR + HIGHER_ACCESS_REQUIRED);
4103 free_recipients(valid_to);
4104 free_recipients(valid_cc);
4105 free_recipients(valid_bcc);
4110 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)
4111 && (CC->user.axlevel < AxNetU) ) {
4112 cprintf("%d Higher access required for network mail.\n",
4113 ERROR + HIGHER_ACCESS_REQUIRED);
4114 free_recipients(valid_to);
4115 free_recipients(valid_cc);
4116 free_recipients(valid_bcc);
4120 if ((RESTRICT_INTERNET == 1)
4121 && (valid_to->num_internet + valid_cc->num_internet + valid_bcc->num_internet > 0)
4122 && ((CC->user.flags & US_INTERNET) == 0)
4123 && (!CC->internal_pgm)) {
4124 cprintf("%d You don't have access to Internet mail.\n",
4125 ERROR + HIGHER_ACCESS_REQUIRED);
4126 free_recipients(valid_to);
4127 free_recipients(valid_cc);
4128 free_recipients(valid_bcc);
4134 /* Is this a room which has anonymous-only or anonymous-option? */
4135 anonymous = MES_NORMAL;
4136 if (CC->room.QRflags & QR_ANONONLY) {
4137 anonymous = MES_ANONONLY;
4139 if (CC->room.QRflags & QR_ANONOPT) {
4140 if (anon_flag == 1) { /* only if the user requested it */
4141 anonymous = MES_ANONOPT;
4145 if ((CC->room.QRflags & QR_MAILBOX) == 0) {
4149 /* Recommend to the client that the use of a message subject is
4150 * strongly recommended in this room, if either the SUBJECTREQ flag
4151 * is set, or if there is one or more Internet email recipients.
4153 if (CC->room.QRflags2 & QR2_SUBJECTREQ) subject_required = 1;
4154 if ((valid_to) && (valid_to->num_internet > 0)) subject_required = 1;
4155 if ((valid_cc) && (valid_cc->num_internet > 0)) subject_required = 1;
4156 if ((valid_bcc) && (valid_bcc->num_internet > 0)) subject_required = 1;
4158 /* If we're only checking the validity of the request, return
4159 * success without creating the message.
4162 cprintf("%d %s|%d\n", CIT_OK,
4163 ((valid_to != NULL) ? valid_to->display_recp : ""),
4165 free_recipients(valid_to);
4166 free_recipients(valid_cc);
4167 free_recipients(valid_bcc);
4171 /* We don't need these anymore because we'll do it differently below */
4172 free_recipients(valid_to);
4173 free_recipients(valid_cc);
4174 free_recipients(valid_bcc);
4176 /* Read in the message from the client. */
4178 cprintf("%d send message\n", START_CHAT_MODE);
4180 cprintf("%d send message\n", SEND_LISTING);
4183 msg = CtdlMakeMessage(&CC->user, recp, cc,
4184 CC->room.QRname, anonymous, format_type,
4185 newusername, newuseremail, subject,
4186 ((!IsEmptyStr(supplied_euid)) ? supplied_euid : NULL),
4189 /* Put together one big recipients struct containing to/cc/bcc all in
4190 * one. This is for the envelope.
4192 char *all_recps = malloc(SIZ * 3);
4193 strcpy(all_recps, recp);
4194 if (!IsEmptyStr(cc)) {
4195 if (!IsEmptyStr(all_recps)) {
4196 strcat(all_recps, ",");
4198 strcat(all_recps, cc);
4200 if (!IsEmptyStr(bcc)) {
4201 if (!IsEmptyStr(all_recps)) {
4202 strcat(all_recps, ",");
4204 strcat(all_recps, bcc);
4206 if (!IsEmptyStr(all_recps)) {
4207 valid = validate_recipients(all_recps, NULL, 0);
4215 msgnum = CtdlSubmitMsg(msg, valid, "", QP_EADDR);
4218 cprintf("%ld\n", msgnum);
4220 cprintf("Message accepted.\n");
4223 cprintf("Internal error.\n");
4225 if (msg->cm_fields['E'] != NULL) {
4226 cprintf("%s\n", msg->cm_fields['E']);
4233 CtdlFreeMessage(msg);
4235 if (valid != NULL) {
4236 free_recipients(valid);
4244 * API function to delete messages which match a set of criteria
4245 * (returns the actual number of messages deleted)
4247 int CtdlDeleteMessages(char *room_name, /* which room */
4248 long *dmsgnums, /* array of msg numbers to be deleted */
4249 int num_dmsgnums, /* number of msgs to be deleted, or 0 for "any" */
4250 char *content_type /* or "" for any. regular expressions expected. */
4253 struct ctdlroom qrbuf;
4254 struct cdbdata *cdbfr;
4255 long *msglist = NULL;
4256 long *dellist = NULL;
4259 int num_deleted = 0;
4261 struct MetaData smi;
4264 int need_to_free_re = 0;
4266 if (content_type) if (!IsEmptyStr(content_type)) {
4267 regcomp(&re, content_type, 0);
4268 need_to_free_re = 1;
4270 CtdlLogPrintf(CTDL_DEBUG, "CtdlDeleteMessages(%s, %d msgs, %s)\n",
4271 room_name, num_dmsgnums, content_type);
4273 /* get room record, obtaining a lock... */
4274 if (CtdlGetRoomLock(&qrbuf, room_name) != 0) {
4275 CtdlLogPrintf(CTDL_ERR, "CtdlDeleteMessages(): Room <%s> not found\n",
4277 if (need_to_free_re) regfree(&re);
4278 return (0); /* room not found */
4280 cdbfr = cdb_fetch(CDB_MSGLISTS, &qrbuf.QRnumber, sizeof(long));
4282 if (cdbfr != NULL) {
4283 dellist = malloc(cdbfr->len);
4284 msglist = (long *) cdbfr->ptr;
4285 cdbfr->ptr = NULL; /* CtdlDeleteMessages() now owns this memory */
4286 num_msgs = cdbfr->len / sizeof(long);
4290 for (i = 0; i < num_msgs; ++i) {
4293 /* Set/clear a bit for each criterion */
4295 /* 0 messages in the list or a null list means that we are
4296 * interested in deleting any messages which meet the other criteria.
4298 if ((num_dmsgnums == 0) || (dmsgnums == NULL)) {
4299 delete_this |= 0x01;
4302 for (j=0; j<num_dmsgnums; ++j) {
4303 if (msglist[i] == dmsgnums[j]) {
4304 delete_this |= 0x01;
4309 if (IsEmptyStr(content_type)) {
4310 delete_this |= 0x02;
4312 GetMetaData(&smi, msglist[i]);
4313 if (regexec(&re, smi.meta_content_type, 1, &pm, 0) == 0) {
4314 delete_this |= 0x02;
4318 /* Delete message only if all bits are set */
4319 if (delete_this == 0x03) {
4320 dellist[num_deleted++] = msglist[i];
4325 num_msgs = sort_msglist(msglist, num_msgs);
4326 cdb_store(CDB_MSGLISTS, &qrbuf.QRnumber, (int)sizeof(long),
4327 msglist, (int)(num_msgs * sizeof(long)));
4330 qrbuf.QRhighest = msglist[num_msgs - 1];
4332 qrbuf.QRhighest = 0;
4334 CtdlPutRoomLock(&qrbuf);
4336 /* Go through the messages we pulled out of the index, and decrement
4337 * their reference counts by 1. If this is the only room the message
4338 * was in, the reference count will reach zero and the message will
4339 * automatically be deleted from the database. We do this in a
4340 * separate pass because there might be plug-in hooks getting called,
4341 * and we don't want that happening during an S_ROOMS critical
4344 if (num_deleted) for (i=0; i<num_deleted; ++i) {
4345 PerformDeleteHooks(qrbuf.QRname, dellist[i]);
4346 AdjRefCount(dellist[i], -1);
4349 /* Now free the memory we used, and go away. */
4350 if (msglist != NULL) free(msglist);
4351 if (dellist != NULL) free(dellist);
4352 CtdlLogPrintf(CTDL_DEBUG, "%d message(s) deleted.\n", num_deleted);
4353 if (need_to_free_re) regfree(&re);
4354 return (num_deleted);
4360 * Check whether the current user has permission to delete messages from
4361 * the current room (returns 1 for yes, 0 for no)
4363 int CtdlDoIHavePermissionToDeleteMessagesFromThisRoom(void) {
4365 CtdlRoomAccess(&CC->room, &CC->user, &ra, NULL);
4366 if (ra & UA_DELETEALLOWED) return(1);
4374 * Delete message from current room
4376 void cmd_dele(char *args)
4385 extract_token(msgset, args, 0, '|', sizeof msgset);
4386 num_msgs = num_tokens(msgset, ',');
4388 cprintf("%d Nothing to do.\n", CIT_OK);
4392 if (CtdlDoIHavePermissionToDeleteMessagesFromThisRoom() == 0) {
4393 cprintf("%d Higher access required.\n",
4394 ERROR + HIGHER_ACCESS_REQUIRED);
4399 * Build our message set to be moved/copied
4401 msgs = malloc(num_msgs * sizeof(long));
4402 for (i=0; i<num_msgs; ++i) {
4403 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4404 msgs[i] = atol(msgtok);
4407 num_deleted = CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4411 cprintf("%d %d message%s deleted.\n", CIT_OK,
4412 num_deleted, ((num_deleted != 1) ? "s" : ""));
4414 cprintf("%d Message not found.\n", ERROR + MESSAGE_NOT_FOUND);
4422 * move or copy a message to another room
4424 void cmd_move(char *args)
4431 char targ[ROOMNAMELEN];
4432 struct ctdlroom qtemp;
4439 extract_token(msgset, args, 0, '|', sizeof msgset);
4440 num_msgs = num_tokens(msgset, ',');
4442 cprintf("%d Nothing to do.\n", CIT_OK);
4446 extract_token(targ, args, 1, '|', sizeof targ);
4447 convert_room_name_macros(targ, sizeof targ);
4448 targ[ROOMNAMELEN - 1] = 0;
4449 is_copy = extract_int(args, 2);
4451 if (CtdlGetRoom(&qtemp, targ) != 0) {
4452 cprintf("%d '%s' does not exist.\n", ERROR + ROOM_NOT_FOUND, targ);
4456 if (!strcasecmp(qtemp.QRname, CC->room.QRname)) {
4457 cprintf("%d Source and target rooms are the same.\n", ERROR + ALREADY_EXISTS);
4461 CtdlGetUser(&CC->user, CC->curr_user);
4462 CtdlRoomAccess(&qtemp, &CC->user, &ra, NULL);
4464 /* Check for permission to perform this operation.
4465 * Remember: "CC->room" is source, "qtemp" is target.
4469 /* Aides can move/copy */
4470 if (CC->user.axlevel >= AxAideU) permit = 1;
4472 /* Room aides can move/copy */
4473 if (CC->user.usernum == CC->room.QRroomaide) permit = 1;
4475 /* Permit move/copy from personal rooms */
4476 if ((CC->room.QRflags & QR_MAILBOX)
4477 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4479 /* Permit only copy from public to personal room */
4481 && (!(CC->room.QRflags & QR_MAILBOX))
4482 && (qtemp.QRflags & QR_MAILBOX)) permit = 1;
4484 /* Permit message removal from collaborative delete rooms */
4485 if (CC->room.QRflags2 & QR2_COLLABDEL) permit = 1;
4487 /* Users allowed to post into the target room may move into it too. */
4488 if ((CC->room.QRflags & QR_MAILBOX) &&
4489 (qtemp.QRflags & UA_POSTALLOWED)) permit = 1;
4491 /* User must have access to target room */
4492 if (!(ra & UA_KNOWN)) permit = 0;
4495 cprintf("%d Higher access required.\n",
4496 ERROR + HIGHER_ACCESS_REQUIRED);
4501 * Build our message set to be moved/copied
4503 msgs = malloc(num_msgs * sizeof(long));
4504 for (i=0; i<num_msgs; ++i) {
4505 extract_token(msgtok, msgset, i, ',', sizeof msgtok);
4506 msgs[i] = atol(msgtok);
4512 err = CtdlSaveMsgPointersInRoom(targ, msgs, num_msgs, 1, NULL, 0);
4514 cprintf("%d Cannot store message(s) in %s: error %d\n",
4520 /* Now delete the message from the source room,
4521 * if this is a 'move' rather than a 'copy' operation.
4524 CtdlDeleteMessages(CC->room.QRname, msgs, num_msgs, "");
4528 cprintf("%d Message(s) %s.\n", CIT_OK, (is_copy ? "copied" : "moved") );
4534 * GetMetaData() - Get the supplementary record for a message
4536 void GetMetaData(struct MetaData *smibuf, long msgnum)
4539 struct cdbdata *cdbsmi;
4542 memset(smibuf, 0, sizeof(struct MetaData));
4543 smibuf->meta_msgnum = msgnum;
4544 smibuf->meta_refcount = 1; /* Default reference count is 1 */
4546 /* Use the negative of the message number for its supp record index */
4547 TheIndex = (0L - msgnum);
4549 cdbsmi = cdb_fetch(CDB_MSGMAIN, &TheIndex, sizeof(long));
4550 if (cdbsmi == NULL) {
4551 return; /* record not found; go with defaults */
4553 memcpy(smibuf, cdbsmi->ptr,
4554 ((cdbsmi->len > sizeof(struct MetaData)) ?
4555 sizeof(struct MetaData) : cdbsmi->len));
4562 * PutMetaData() - (re)write supplementary record for a message
4564 void PutMetaData(struct MetaData *smibuf)
4568 /* Use the negative of the message number for the metadata db index */
4569 TheIndex = (0L - smibuf->meta_msgnum);
4571 cdb_store(CDB_MSGMAIN,
4572 &TheIndex, (int)sizeof(long),
4573 smibuf, (int)sizeof(struct MetaData));
4578 * AdjRefCount - submit an adjustment to the reference count for a message.
4579 * (These are just queued -- we actually process them later.)
4581 void AdjRefCount(long msgnum, int incr)
4583 struct arcq new_arcq;
4586 CtdlLogPrintf(CTDL_DEBUG, "AdjRefCount() msg %ld ref count delta %+d\n",
4590 begin_critical_section(S_SUPPMSGMAIN);
4591 if (arcfp == NULL) {
4592 arcfp = fopen(file_arcq, "ab+");
4594 end_critical_section(S_SUPPMSGMAIN);
4596 /* msgnum < 0 means that we're trying to close the file */
4598 CtdlLogPrintf(CTDL_DEBUG, "Closing the AdjRefCount queue file\n");
4599 begin_critical_section(S_SUPPMSGMAIN);
4600 if (arcfp != NULL) {
4604 end_critical_section(S_SUPPMSGMAIN);
4609 * If we can't open the queue, perform the operation synchronously.
4611 if (arcfp == NULL) {
4612 TDAP_AdjRefCount(msgnum, incr);
4616 new_arcq.arcq_msgnum = msgnum;
4617 new_arcq.arcq_delta = incr;
4618 rv = fwrite(&new_arcq, sizeof(struct arcq), 1, arcfp);
4626 * TDAP_ProcessAdjRefCountQueue()
4628 * Process the queue of message count adjustments that was created by calls
4629 * to AdjRefCount() ... by reading the queue and calling TDAP_AdjRefCount()
4630 * for each one. This should be an "off hours" operation.
4632 int TDAP_ProcessAdjRefCountQueue(void)
4634 char file_arcq_temp[PATH_MAX];
4637 struct arcq arcq_rec;
4638 int num_records_processed = 0;
4640 snprintf(file_arcq_temp, sizeof file_arcq_temp, "%s.%04x", file_arcq, rand());
4642 begin_critical_section(S_SUPPMSGMAIN);
4643 if (arcfp != NULL) {
4648 r = link(file_arcq, file_arcq_temp);
4650 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4651 end_critical_section(S_SUPPMSGMAIN);
4652 return(num_records_processed);
4656 end_critical_section(S_SUPPMSGMAIN);
4658 fp = fopen(file_arcq_temp, "rb");
4660 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4661 return(num_records_processed);
4664 while (fread(&arcq_rec, sizeof(struct arcq), 1, fp) == 1) {
4665 TDAP_AdjRefCount(arcq_rec.arcq_msgnum, arcq_rec.arcq_delta);
4666 ++num_records_processed;
4670 r = unlink(file_arcq_temp);
4672 CtdlLogPrintf(CTDL_CRIT, "%s: %s\n", file_arcq_temp, strerror(errno));
4675 return(num_records_processed);
4681 * TDAP_AdjRefCount - adjust the reference count for a message.
4682 * This one does it "for real" because it's called by
4683 * the autopurger function that processes the queue
4684 * created by AdjRefCount(). If a message's reference
4685 * count becomes zero, we also delete the message from
4686 * disk and de-index it.
4688 void TDAP_AdjRefCount(long msgnum, int incr)
4691 struct MetaData smi;
4694 /* This is a *tight* critical section; please keep it that way, as
4695 * it may get called while nested in other critical sections.
4696 * Complicating this any further will surely cause deadlock!
4698 begin_critical_section(S_SUPPMSGMAIN);
4699 GetMetaData(&smi, msgnum);
4700 smi.meta_refcount += incr;
4702 end_critical_section(S_SUPPMSGMAIN);
4703 CtdlLogPrintf(CTDL_DEBUG, "TDAP_AdjRefCount() msg %ld ref count delta %+d, is now %d\n",
4704 msgnum, incr, smi.meta_refcount
4707 /* If the reference count is now zero, delete the message
4708 * (and its supplementary record as well).
4710 if (smi.meta_refcount == 0) {
4711 CtdlLogPrintf(CTDL_DEBUG, "Deleting message <%ld>\n", msgnum);
4713 /* Call delete hooks with NULL room to show it has gone altogether */
4714 PerformDeleteHooks(NULL, msgnum);
4716 /* Remove from message base */
4718 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4719 cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
4721 /* Remove metadata record */
4722 delnum = (0L - msgnum);
4723 cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
4729 * Write a generic object to this room
4731 * Note: this could be much more efficient. Right now we use two temporary
4732 * files, and still pull the message into memory as with all others.
4734 void CtdlWriteObject(char *req_room, /* Room to stuff it in */
4735 char *content_type, /* MIME type of this object */
4736 char *raw_message, /* Data to be written */
4737 off_t raw_length, /* Size of raw_message */
4738 struct ctdluser *is_mailbox, /* Mailbox room? */
4739 int is_binary, /* Is encoding necessary? */
4740 int is_unique, /* Del others of this type? */
4741 unsigned int flags /* Internal save flags */
4745 struct ctdlroom qrbuf;
4746 char roomname[ROOMNAMELEN];
4747 struct CtdlMessage *msg;
4748 char *encoded_message = NULL;
4750 if (is_mailbox != NULL) {
4751 CtdlMailboxName(roomname, sizeof roomname, is_mailbox, req_room);
4754 safestrncpy(roomname, req_room, sizeof(roomname));
4757 CtdlLogPrintf(CTDL_DEBUG, "Raw length is %ld\n", (long)raw_length);
4760 encoded_message = malloc((size_t) (((raw_length * 134) / 100) + 4096 ) );
4763 encoded_message = malloc((size_t)(raw_length + 4096));
4766 sprintf(encoded_message, "Content-type: %s\n", content_type);
4769 sprintf(&encoded_message[strlen(encoded_message)],
4770 "Content-transfer-encoding: base64\n\n"
4774 sprintf(&encoded_message[strlen(encoded_message)],
4775 "Content-transfer-encoding: 7bit\n\n"
4781 &encoded_message[strlen(encoded_message)],
4789 &encoded_message[strlen(encoded_message)],
4795 CtdlLogPrintf(CTDL_DEBUG, "Allocating\n");
4796 msg = malloc(sizeof(struct CtdlMessage));
4797 memset(msg, 0, sizeof(struct CtdlMessage));
4798 msg->cm_magic = CTDLMESSAGE_MAGIC;
4799 msg->cm_anon_type = MES_NORMAL;
4800 msg->cm_format_type = 4;
4801 msg->cm_fields['A'] = strdup(CC->user.fullname);
4802 msg->cm_fields['O'] = strdup(req_room);
4803 msg->cm_fields['N'] = strdup(config.c_nodename);
4804 msg->cm_fields['H'] = strdup(config.c_humannode);
4805 msg->cm_flags = flags;
4807 msg->cm_fields['M'] = encoded_message;
4809 /* Create the requested room if we have to. */
4810 if (CtdlGetRoom(&qrbuf, roomname) != 0) {
4811 CtdlCreateRoom(roomname,
4812 ( (is_mailbox != NULL) ? 5 : 3 ),
4813 "", 0, 1, 0, VIEW_BBS);
4815 /* If the caller specified this object as unique, delete all
4816 * other objects of this type that are currently in the room.
4819 CtdlLogPrintf(CTDL_DEBUG, "Deleted %d other msgs of this type\n",
4820 CtdlDeleteMessages(roomname, NULL, 0, content_type)
4823 /* Now write the data */
4824 CtdlSubmitMsg(msg, NULL, roomname, 0);
4825 CtdlFreeMessage(msg);
4833 void CtdlGetSysConfigBackend(long msgnum, void *userdata) {
4834 config_msgnum = msgnum;
4838 char *CtdlGetSysConfig(char *sysconfname) {
4839 char hold_rm[ROOMNAMELEN];
4842 struct CtdlMessage *msg;
4845 strcpy(hold_rm, CC->room.QRname);
4846 if (CtdlGetRoom(&CC->room, SYSCONFIGROOM) != 0) {
4847 CtdlGetRoom(&CC->room, hold_rm);
4852 /* We want the last (and probably only) config in this room */
4853 begin_critical_section(S_CONFIG);
4854 config_msgnum = (-1L);
4855 CtdlForEachMessage(MSGS_LAST, 1, NULL, sysconfname, NULL,
4856 CtdlGetSysConfigBackend, NULL);
4857 msgnum = config_msgnum;
4858 end_critical_section(S_CONFIG);
4864 msg = CtdlFetchMessage(msgnum, 1);
4866 conf = strdup(msg->cm_fields['M']);
4867 CtdlFreeMessage(msg);
4874 CtdlGetRoom(&CC->room, hold_rm);
4876 if (conf != NULL) do {
4877 extract_token(buf, conf, 0, '\n', sizeof buf);
4878 strcpy(conf, &conf[strlen(buf)+1]);
4879 } while ( (!IsEmptyStr(conf)) && (!IsEmptyStr(buf)) );
4885 void CtdlPutSysConfig(char *sysconfname, char *sysconfdata) {
4886 CtdlWriteObject(SYSCONFIGROOM, sysconfname, sysconfdata, (strlen(sysconfdata)+1), NULL, 0, 1, 0);
4891 * Determine whether a given Internet address belongs to the current user
4893 int CtdlIsMe(char *addr, int addr_buf_len)
4895 struct recptypes *recp;
4898 recp = validate_recipients(addr, NULL, 0);
4899 if (recp == NULL) return(0);
4901 if (recp->num_local == 0) {
4902 free_recipients(recp);
4906 for (i=0; i<recp->num_local; ++i) {
4907 extract_token(addr, recp->recp_local, i, '|', addr_buf_len);
4908 if (!strcasecmp(addr, CC->user.fullname)) {
4909 free_recipients(recp);
4914 free_recipients(recp);
4920 * Citadel protocol command to do the same
4922 void cmd_isme(char *argbuf) {
4925 if (CtdlAccessCheck(ac_logged_in)) return;
4926 extract_token(addr, argbuf, 0, '|', sizeof addr);
4928 if (CtdlIsMe(addr, sizeof addr)) {
4929 cprintf("%d %s\n", CIT_OK, addr);
4932 cprintf("%d Not you.\n", ERROR + ILLEGAL_VALUE);
4938 /*****************************************************************************/
4939 /* MODULE INITIALIZATION STUFF */
4940 /*****************************************************************************/
4942 CTDL_MODULE_INIT(msgbase)
4945 CtdlRegisterProtoHook(cmd_msgs, "MSGS", "Output a list of messages in the current room");
4946 CtdlRegisterProtoHook(cmd_msg0, "MSG0", "Output a message in plain text format");
4947 CtdlRegisterProtoHook(cmd_msg2, "MSG2", "Output a message in RFC822 format");
4948 CtdlRegisterProtoHook(cmd_msg3, "MSG3", "Output a message in raw format (deprecated)");
4949 CtdlRegisterProtoHook(cmd_msg4, "MSG4", "Output a message in the client's preferred format");
4950 CtdlRegisterProtoHook(cmd_msgp, "MSGP", "Select preferred format for MSG4 output");
4951 CtdlRegisterProtoHook(cmd_opna, "OPNA", "Open an attachment for download");
4952 CtdlRegisterProtoHook(cmd_dlat, "DLAT", "Download an attachment");
4953 CtdlRegisterProtoHook(cmd_ent0, "ENT0", "Enter a message");
4954 CtdlRegisterProtoHook(cmd_dele, "DELE", "Delete a message");
4955 CtdlRegisterProtoHook(cmd_move, "MOVE", "Move or copy a message to another room");
4956 CtdlRegisterProtoHook(cmd_isme, "ISME", "Determine whether an email address belongs to a user");
4959 /* return our Subversion id for the Log */